Events

The single event shape every source produces, and the names that mean something.

Everything Pivolio ingests — SDK, raw HTTP, webhook, Shopify — becomes the same structure: a UnifiedEvent. One shape means identity resolution, attribution and destination mapping never have to care where an event came from.

Required fields

A raw HTTP event must carry all four of these, or the collector returns 422:

event_id
stringRequired

A ULID. Doubles as the idempotency key — re-sending the same event_id for a workspace returns {"ok": true, "duplicate": true} and does no further work.

event_name
stringRequired

Matches ^[a-zA-Z0-9_.]{1,128}$.

event_time
floatRequired

Unix seconds, not milliseconds. A millisecond timestamp puts your event tens of thousands of years in the future.

anonymous_id
stringRequired

The device-level identifier. The SDK manages this for you.

Optional: user_id, traits, context, consent, properties, test_mode, and schema_version (defaults to "1").

Traits and context

traits carry person-level data — email, phone, first_name, last_name, country, city, state, zip, gender, date_of_birth, external_id, plus any custom keys you add. These are what get normalized and hashed before delivery, and what drive match quality.

context carries the environment:

  • context.click_ids — 14 supported: fbclid, fbp, fbc, gclid, gbraid, wbraid, ttclid, ttp, li_fat_id, msclkid, epik, rdt_cid, sc_click_id, twclid.
  • context.campaignsource, medium, name, term, content.
  • context.pageurl, path, referrer, title.
  • context.devicetype, os, os_version, browser, browser_version.
  • context.sessionid (required if you send session), started_at.

context.ip is server-set. Whatever a client sends is overwritten with the observed IP — you can’t spoof geo or traffic-quality signals from the browser.

Properties

properties is free-form, capped at 64,000 bytes JSON-encoded. Four keys are read by the pipeline rather than just stored:

  • revenue or value → the event’s revenue, rounded to cents.
  • currency → a 3-character code, uppercased.
  • order_id → the commerce order reference, used for deduplication at destinations that support it.

Properties are nested, not top-level:

1track('order_completed', {
2 properties: { revenue: 99.5, currency: 'USD', order_id: 'ORD-1041' },
3});

The SDK’s commerce helpers (trackPurchase, trackAddToCart, trackBeginCheckout) exist mostly to get this mapping right for you — notably orderIdorder_id.

Standard event names

Twenty-five names are recognized. Using one gets you destination mapping and correct taxonomy for free; anything else is a custom event.

GroupNames
Browsingpage_view, view_content, search
Commerceadd_to_cart, add_to_wishlist, initiate_checkout, begin_checkout, add_payment_info, order_completed, purchase
Lead & registrationlead_submitted, lead, complete_registration, sign_up, contact, subscribe, start_trial, submit_application, schedule
Engagementclick_button, download, donate, find_location, key_page_view
Identityidentify

product_viewed is not a standard name — the one you want is view_content. A near-miss name is silently treated as a custom event, so it maps to nothing at Meta, TikTok or Reddit and you lose the conversion.

Custom events

Any name matching ^[a-zA-Z0-9_.]{1,128}$ is accepted. Custom events are tracked, stored and reported normally. What they don’t get is a standard destination mapping, so by default no destination sends them. To deliver a custom event, map it explicitly on each destination that should receive it.

Taxonomy

Every event lands in one of three tiers. This is what decides whether an event earns attribution credit and triggers click backfill.

  • Conversionorder_completed, purchase, lead_submitted, lead, complete_registration, sign_up, subscribe, start_trial, submit_application, schedule, contact, donate
  • Intentadd_to_cart, add_to_wishlist, initiate_checkout, begin_checkout, add_payment_info, view_content, search
  • Browse — everything else, including custom events

Two of those placements are deliberate and worth knowing:

initiate_checkout and begin_checkout are intent, not conversion. A started checkout is a strong signal, but it isn’t an outcome — treating it as one inflates your conversion counts with abandoned carts.

add_payment_info is intent for the same reason, only sharper. Counting it as a conversion double-counted checkouts: the same session produced a payment-info event and a purchase, and both claimed credit.