Duplicate purchases after a Conversions API install almost always mean one thing: the browser and the server are sending different event_id values for the same purchase. Meta deduplicates on matching event_name plus event_id. If the IDs differ, it has no way to know the two events are the same order, so it counts both.
The dangerous version is the opposite failure, where IDs collide and you silently lose real sales.
Installing the Conversions API is the right decision. Browser tracking loses events to ad blockers, tracking prevention, broken sessions and abandoned page loads, and a server-side feed recovers a good share of them. Almost everyone should be running both.
But “running both” means Meta now receives two reports of every purchase, and the only thing stopping it counting each one twice is a shared identifier. Get that identifier wrong in one direction and your revenue doubles on paper. Get it wrong in the other and your revenue quietly halves.
How deduplication actually works
Meta matches on two fields together: event_name and event_id. Both must agree.
Browser: event_name: "Purchase" event_id: "a91f-2026-08-12" Server: event_name: "Purchase" event_id: "1047"
Browser: event_name: "Purchase" event_id: "order_1047" Server: event_name: "Purchase" event_id: "order_1047"
The rule is simple to state and easy to break in practice, because the two sides are usually built by different systems at different times. The browser event is generated by a theme or a tag manager; the server event is generated by an app, a middleware service, or your own code. Nothing forces them to agree, and nothing warns you when they do not.
Choosing an identifier that cannot drift
The identifier must be unique per order and identical across both paths. Those two requirements rule out most of what people reach for first.
| Candidate | Unique per order? | Same on both sides? | Verdict |
|---|---|---|---|
| Order ID / order number | Yes | Yes, if both read the order | Use this |
| Randomly generated UUID | Yes | Only if generated once and passed | Risky |
| Checkout ID | Not always | Often differs from order ID | Avoid |
| Session ID | No | No | Never |
| Timestamp | No | Almost never | Never |
Timestamps are the worst offender and the most tempting, because they look unique. They are not: two orders in the same second collide, and the browser’s clock is not your server’s clock. A UUID is fine only if it is generated once and then passed to the other side — if each side generates its own, you have built a duplication machine.
Everyone worries about double-counting because it is visible. The worse bug is an identifier that is not unique — a checkout ID reused across orders, or a value that repeats. Then Meta correctly deduplicates two genuinely different purchases into one, and you lose real revenue from your reporting with no error anywhere.
Double-counting looks like good news and gets investigated. Silent loss looks like a slow decline and gets blamed on the ads.
Proving it works — not assuming it does
Configured is not the same as working. Here is the test that settles it, and it takes about fifteen minutes.
- Place one real orderA real purchase through the real checkout, not a test link. Note the order number.
- Capture the browser event IDOpen DevTools before checking out, filter the Network tab for
facebook, complete the order, and read theeventIDon the Purchase request. - Capture the server event IDFrom your CAPI integration’s logs, or from Events Manager, find the server-side event for that same order and read its
event_id. - Compare them character for characterNot similar. Identical. A prefix on one side (
order_1047versus1047) is a failure, not a detail. - Check the deduplication reportingIn Events Manager, open the Purchase event and look at how it is being received. A correctly deduplicated setup shows both browser and server as sources for the same event, not two separate event totals.
- Reconcile a full weekCompare your store’s order count with Events Manager purchases for a settled week. Meaningfully above your order count means duplication. Meaningfully below means loss.
The number that tells you it is working
Once deduplication is right, the metric worth watching is Event Match Quality. It reflects how much identifying information you send with each event — hashed email, phone, name, location, click identifiers — and it drives how well Meta can attribute a conversion to the person who saw the ad.
This is where the Conversions API earns its keep. Your server knows the customer’s email and phone number because they just bought something. The browser often does not. Sending that data server-side, correctly hashed, is usually a bigger win than recovering the blocked events in the first place.
Customer identifiers must be SHA-256 hashed before they are sent. This is not a performance nicety — it is the difference between sending Meta a hash and sending Meta your customers’ raw personal data. If you are building the integration yourself, verify the hashing on a real payload before you go live, because a bug here is a data protection incident rather than a tracking bug.
What I am not claiming
I have not given you a target Event Match Quality score or an expected uplift from installing CAPI. Both depend entirely on what customer data you can legitimately collect, your checkout, and your traffic — and every benchmark I have seen published comes from a vendor whose product improves the number they are benchmarking.
Measure your own baseline before the change, measure again after it has run for a full attribution window, and compare against yourself. That comparison is worth more than anyone’s published average.
Not sure if your deduplication is working?
It is a fifteen-minute check and most setups fail it. Send me your store URL and I will tell you what I find.
Get a free tracking audit →