Why two keys
An event id identifies one action as observed by your systems. An order id identifies one business transaction. They fail in different ways:
- Event ids are generated per observation. If the browser and the server observe the same purchase independently, they only share an id if you deliberately pass it between them.
- Order ids exist for purchases and refunds but not for page views, leads or sign-ups.
A robust setup uses both: one event id generated at the source and handed to every path, plus the order id on every commerce event. Platforms that dedupe on the event id merge the observation; platforms that dedupe on the order id merge the transaction; a platform that supports both gets belt and braces.
The reference table
| Platform | Browser field | Server field | Merges on | Notes |
|---|---|---|---|---|
| Meta | eventID (pixel option) | event_id | event id + event name, ~48 h | order_id in custom_data is informational |
| Google Ads | transaction_id (gtag) | orderId (upload) | order id per conversion action | leads: use separate conversion actions |
| GA4 | transaction_id | transaction_id | transaction id for purchase | other events are not deduplicated |
| TikTok | event_id (ttq option) | event_id | event id + event name | order_id in properties |
| Microsoft | event_id (UET push) | eventId | event id + event name | same UET tag id on both paths |
event_id (lintrk) | eventId | event id | per conversion rule | |
event_id (pintrk) | event_id | event id | order_id in custom_data | |
| Snapchat | client_dedup_id | event_id | matching pair | within the dedup window |
conversionId (rdt) | conversion_id | conversion id | ||
| X | conversion_id (twq) | conversion_id | conversion id | per event id (tw-…) |
| Campaign Manager 360 | ordinal / u1 | ordinal | ordinal + activity + user | timestamps within 28 days |
| Affiliate networks | — | order reference | order reference | network-side duplicate protection |
Generating the id
Generate the id before anything is sent, at the place the action is first known. In the browser that is the moment tsq.push(["track", ...]) runs; Track assigns a ULID and uses it for the vendor pixel call and the collector request alike. For server sources, the source system should generate the id — or, for purchases, pass the order id so the router can derive a deterministic event id from it.
A deterministic id for purchases (hash(site, "purchase", order_id)) has a pleasant property: a retried webhook from Shopify or a duplicate CRM export produces the same id, and the worker's own dedup guard drops it before delivery.
Timing windows
Vendors only merge within a window — Meta documents about 48 hours, others are similar. If your server event arrives days later (a CRM batch), it will not merge with the browser event; it will count in addition. Choose per event type:
- Purchases and other immediate conversions: hybrid with a shared id, both within minutes.
- Delayed qualifications (a lead becomes an opportunity a week later): a different vendor event or conversion action, not a duplicate of the browser one.
Refunds
Refunds are their own events. GA4 has a refund event with transaction_id; Google Ads uses conversion adjustments (retraction/restatement) keyed on the order id; Meta has no refund event but accepts negative values in custom conversions; most affiliate networks accept a reversal postback. Track emits refund with a negative value and the original order id and lets each connector decide what the vendor supports.
A debugger-driven verification
- Buy something in test mode with the browser open.
- In the event debugger find the browser purchase and the server purchase — same event id, same order id.
- Open the destination monitor: one delivery attempt per path, both accepted.
- In the vendor's UI (Events Manager, Test events, DebugView), confirm one conversion, not two.
If step 4 shows two, the id did not travel through one of the paths. The redacted payload preview in the debugger shows which.