Meta Counts Purchases Twice: Pixel + CAPI Deduplication Fix
Your store did 100 orders yesterday. Meta Events Manager shows 195 purchases. The cause in almost every case I audit: the browser pixel and the Conversions API both send a Purchase event for the same order, and they carry no shared event_id, so Meta has no way to know it is one sale and counts it twice.
This is not a display bug. The doubled events feed campaign optimization and reported ROAS. Your ads look twice as profitable as they are, and budget decisions get made on that number. Here is how the deduplication mechanism works, how to confirm it is broken, and how to fix it.
How Meta deduplication actually works
Running the pixel and the Conversions API together is the correct setup. Meta recommends it because the server event survives ad blockers and browser restrictions. The system is designed to receive the same purchase twice and merge it. The merge has one rule:
- Two events are deduplicated when they have the same event_name and the same event_id, received within 48 hours of each other.
- Meta keeps the first event that arrives and drops the duplicate.
The match is a literal string comparison. order_10471 does not match 10471. Note the naming difference: in the browser pixel the parameter is eventID (an option on the fbq call), in the Conversions API payload it is event_id. Different spelling, but the values must be identical.
If the IDs never match, or one side sends no ID at all, Meta treats every order as two independent purchases. There is no reliable fallback you control. The event_id is the whole mechanism.
How to confirm it in Events Manager
Open Events Manager, select your pixel dataset, and click into the Purchase event details. Three things to check:
- Connection method breakdown. The event detail view splits Purchase by Browser and Server. In a healthy setup both channels report, but the total after deduplication is close to your real order count. In a broken setup the two channels simply stack.
- Deduplicated count. Meta reports how many server events were deduplicated against browser events. Both channels active plus a deduplicated figure near zero is the smoking gun.
- The ratio. Compare Purchase count in Events Manager against orders in your store admin for the same date range. A ratio near 2.0 means dedup is fully broken. Between 1.0 and 2.0 usually means the browser event is sometimes blocked, so only part of the traffic doubles.
Then run a live check in the Test Events tab: place a test order and watch. You should see two Purchase events arrive within seconds, event IDs visible on both, and the second one labeled Deduplicated. If both show up and neither carries that label, you have your confirmation.
The setups that break it
I see the same handful of broken configurations on repeat:
- Plugin sends a random ID, pixel sends none. A CAPI plugin generates its own UUID per request while the pixel snippet in the theme has no eventID parameter. Nothing can ever match.
- Two independent stacks. The platform integration (Shopify's Facebook and Instagram app, Facebook for WooCommerce) plus a second pixel pasted into the theme or fired from GTM. Each stack may dedupe internally, but events across stacks never match, and Purchase fires from both.
- Server GTM with a broken handoff. The web container generates an event ID, but the Meta tag in the server container reads a different variable, or the web pixel tag never got the eventID field filled in. One side has an ID, the other has a different one or none.
- Webhook vs thank-you page. Server Purchase fires from an order webhook keyed on the order ID, browser Purchase fires on the confirmation page with a randomly generated ID. Both send IDs, the IDs never agree.
Common thread: both sides sending is fine, both sides inventing their own ID is not.
The fix: one order-based event_id on both sides
Derive the event_id from the order itself, deterministically, and use the identical string on both channels. Order number or order ID is the right key: both the browser and the server already know it, and it never changes on retries.
fbq('track', 'Purchase',
{ value: 89.00, currency: 'EUR' },
{ eventID: 'order_10471' }
);Conversions API payload for the same order:
{
"data": [{
"event_name": "Purchase",
"event_id": "order_10471",
"event_time": 1767800000,
"action_source": "website",
...
}]
}In a server GTM setup, push the order ID into the dataLayer on the purchase page, map it into the web Meta Pixel tag's event ID field, and make sure the same value rides to the server container and into the Event ID field of the Conversions API tag there. One value, one source, two destinations.
If your plugin does not let you control the event_id, replace it or disable one of its channels. Do not run two full pixel-plus-CAPI stacks side by side and hope.
Verify before you trust the numbers again
Do not declare this fixed until you have watched a real event travel both paths with the same ID. My verification pass:
- Browser network tab. Open DevTools, Network tab, filter for facebook.com/tr, place a test order. Open the Purchase request and find the eid parameter. That is the browser-side event_id. If it is missing, the pixel side is still naked.
- Tag Assistant / GTM preview. In web preview, confirm the Meta Pixel tag fires on the confirmation page and its event ID variable resolves to the order value, not undefined. In server container preview, confirm the incoming request carries the ID and the outgoing Meta request includes it as event_id.
- GA4 DebugView, if your server container is fed by the GA4 stream: confirm the purchase event arrives with the transaction or event ID parameter that your server-side Meta tag maps from. If it is empty there, it is empty everywhere downstream.
- Events Manager Test Events. Add your test_event_code to the server payload, place an order, and watch for the pair: browser Purchase first, server Purchase seconds later marked Deduplicated, both showing the identical ID.
- Next-day sanity check. Compare the Events Manager Purchase total against store orders for the day. Within a few percent is normal. Anything drifting back toward 2x means one of the sources regressed.
Numbers still don't add up?
Tell my assistant what you see and I will reach out to you directly. Every rated job on Upwork is 5.0, check them live.
FAQ
Why does Meta show more purchases than my Shopify or WooCommerce store?
Your browser pixel and your Conversions API integration are both reporting each order as a separate Purchase event. Without a matching event_id on both events, Meta cannot deduplicate them, so every order counts twice. A ratio near 2x almost always means dedup is fully broken rather than a tracking gap.
Does the event_id have to be identical on the pixel and the Conversions API?
Yes. Meta deduplicates only when event_name and event_id both match as literal strings, within a 48-hour window. The browser parameter is spelled eventID and the server parameter event_id, but the values must be character-for-character the same.
Should I just turn off the browser pixel and keep only the Conversions API?
No. The redundant setup exists for a reason: the server event survives ad blockers and browser privacy restrictions, while the browser event contributes signals the server often lacks. Fix the shared event_id instead of amputating a channel.
Can I use a random UUID as the event_id?
Only if it is generated once and passed to both the pixel and the server event. Two sides independently generating random IDs will never match. An ID derived from the order number is safer because both sides already know it and webhook retries produce the same ID instead of a new duplicate.
Want it checked by professionals?
Seven days of platform conversions next to your real orders is enough for us to name the likely cause. No charge, usually under an hour.