Deduplication beyond event_id: the three duplicates left over
event_id solves browser versus server. Three sources of duplication remain that it never reaches — and the biggest one rewrote nearly half of a week's events in an account we measured.

Every Conversions API guide gives event_id a paragraph, and rightly so: without it, keeping the browser pixel and turning on server-side sending makes Meta count every purchase twice.
What almost no guide says is that event_id solves exactly one case — the same event arriving through two channels. There are three others, and none of them has anything to do with the browser.
Duplicate 1: the webhook and the history sync
The sale arrives in real time through the checkout webhook. An hour later, the sync that keeps reports current sweeps the same window and finds the same sale. If that sync also emits events, the purchase becomes a conversion again — and again, every hour.
In one account we measured, this produced 17,108 person-events in a week for 8,952 distinct real events. Nearly half the volume was a byte-identical rewrite of what had already been emitted. No screen showed this: the sync reported success, because it had indeed run.
The rule that fixes it: the webhook emits conversions, the sync feeds reports. When the sync finds something the webhook missed, then it emits — once, and marks that it did.
Duplicate 2: two events for one sale
Several platforms emit one event when the purchase is approved and another when it is completed, days later, at the end of the refund window. Those are two different names for the same transaction.
Filtering both as conversions doubles your revenue. Filtering only the second delays campaign learning by a full week. Filtering only the first is almost always right — and it is a decision, not a technical detail.
Duplicate 3: redelivery
Every reliable queue redelivers a message when it does not get an acknowledgement. That is a quality, not a defect: it is what stops events being lost when something fails midway.
The side effect is that the same row can be processed twice. If the write is not idempotent — if it inserts rather than upserting on a stable key — every redelivery becomes a duplicate.
The timestamp-in-the-key trap
This is the subtlest one and the hardest to find. It is common for the deduplication key to include the event timestamp. It makes sense on paper: two identical events at different moments are two events.
The problem appears when the timestamp is the processing moment rather than the moment the thing happened. Then every reprocess generates a fresh stamp, the key changes, and deduplication never collapses anything — the mechanism exists, runs, reports success and deduplicates nothing.
key = order + type + now() <- never collapses
key = order + type <- always collapses
key = order + type + order_date <- collapses, and preserves historyThe general rule
The deduplication key has to be the event's business identity: the order number, the transaction identifier, the person and product pair. Never a value generated at send time, and never a stamp that changes on every run.
It is also worth remembering that Meta's protection has an expiry: it treats two sends with the same name and same event_id as one within seven days. Resending an old sale after that window is a brand new conversion in its eyes, identical event_id and all.
In CrazyLeads the deduplication key comes from the source's business identity, the sync emits only what is new, and the write is idempotent on that key — a queue redelivery does not become a new row.