Meta treats a pixel event and a Conversions API event as one only when both have the same event name and the same event ID (eventID in the pixel, event_id on the server) and arrive within 48 hours of each other. On Shopify it usually fails because each side makes up its own ID. Use one value both sides already know, such as the Shopify order ID for purchases.
Sending purchases through both the Meta pixel and the Conversions API is the setup Meta recommends. The browser catches the session details, the server catches the purchases the browser misses. But it only works if Meta can tell that the two events describe the same order. When it can't, every purchase gets counted twice.
That matching is called deduplication, and it runs on one field: the event ID. This guide explains exactly how Meta uses it, the six ways it goes wrong on Shopify, and how to prove with one test order that it's fixed. If you already know Meta shows more purchases than Shopify, start with Meta duplicate purchases on Shopify, which covers every cause, then come back here for the event ID side.
How Meta deduplicates events
Meta's documented rule has three parts. A browser event and a server event count as one when:
- The event names match.
Purchasein the pixel andPurchasein the Conversions API. - The event IDs match. In the pixel, the ID is
eventID, passed as the fourth argument offbq('track', ...). In the Conversions API, it isevent_id. - They arrive within 48 hours. Meta only deduplicates events received within 48 hours of the first event it got with that ID.
A purchase sent correctly from both sides looks like this:
// Browser (pixel)
fbq('track', 'Purchase', { value: 84.00, currency: 'USD' }, { eventID: '5739204817' });
// Server (Conversions API)
{
"event_name": "Purchase",
"event_id": "5739204817",
"event_time": 1762330000,
"action_source": "website",
...
}When both match, Meta generally keeps whichever arrived first and discards the other. There is a second, weaker method based on the fbp cookie or external_id, but it generally only works when the browser event arrives before the server one, and it can't catch duplicates from a single source. For purchases, use event IDs.
It is designed to merge one browser event with one server event. If two tools both send purchases from the browser, or two apps both send them from the server, they almost never share an ID. Remove the extra source instead of trying to make the IDs match.
Check whether it is working
- Compare counts first. In Events Manager, open your dataset and note Purchase events for the last 7 days. Compare with Shopify online store orders for the same days. If Meta's number is more than about 5% higher, something is not being deduplicated.
- Open the Purchase event. Click the Purchase row in Overview. You should see both Browser and Server as connection methods, and the event details show how Meta is deduplicating them and whether event IDs are being received.
- Read Diagnostics. Warnings about duplicate events or missing deduplication keys tell you which event is affected. Fix Purchase first, then the checkout steps, because those are what your campaigns optimize on.
The 6 reasons it fails
1. Each side makes up its own ID
The most common cause. The browser generates a random ID when the page loads, the server generates a different one when the order webhook arrives, and they never match. Both events are counted.
2. Browser and server come from different tools
For example, the Facebook and Instagram app sends the pixel, and a tracking app or server-side GTM sends the Conversions API. Each tool uses its own ID scheme. Unless you've configured both to use the same value, they won't match. The usual fix is to let one tool own both sides.
3. The ID is missing on one side
Older theme code calls fbq('track', 'Purchase', ...) with no fourth argument, so the browser event has no ID at all. Or a server integration leaves event_id empty. An event without an ID can't be matched to anything.
4. The event names differ
Purchase in the browser and a custom name like purchase_complete on the server are different events to Meta, even with the same ID. Use the standard event names on both sides.
5. The server event arrives too late
Events more than 48 hours apart are not deduplicated. This happens with batch uploads, delayed webhooks retried days later, or orders that are only sent once they're paid or fulfilled.
6. The ID changes when the page reloads
If the purchase fires on the order status page and the ID is generated on each load, a customer who returns to check their order sends a new purchase with a new ID. Fire purchases on Shopify's checkout_completed event, which happens once per order, and use a stable ID.
Getting event IDs right on Shopify
The rule is to use a value that both the browser and the server already know for the same action. For each Shopify event, that usually means:
| Event | Good event ID | Where each side gets it |
|---|---|---|
| Purchase | Shopify order ID | Browser: checkout.order.id in the checkout_completed custom pixel event. Server: id in the orders/create webhook. Make sure both sides use the same format, for example the plain number without a gid:// prefix. |
| InitiateCheckout, AddPaymentInfo | The Shopify event ID | The id of the customer event, passed to the server along with the event. |
| AddToCart, ViewContent | The Shopify event ID | Same as above. These are only sent from the server if your setup relays browser events. |
In practice, most stores don't write this themselves. There are three clean options:
- The Facebook and Instagram app alone, at its highest data sharing level. It sends both the pixel and the Conversions API and handles the IDs. Nothing else should send to the same pixel.
- A tracking app such as Elevar or Littledata, which owns both sides. The Facebook app's own data sharing then has to be turned down, as the app's setup guide describes.
- Server-side GTM, where you build both tags and pass the same ID to each. Most control, most work. See Stape vs Elevar for how the options compare.
Mixing them is what breaks deduplication. Pick one owner for Meta events and remove the rest.
Our free tracking check lists every source sending events to your pixel, checks whether purchases are being deduplicated and tells you exactly what to remove. Read-only, within 3 business days.
Get a free tracking checkTest it with one order
- Open Test events. In Events Manager, go to your dataset, Test events. If your server setup supports a test event code, add it so server events show up there too.
- Place an order. Use a 100% discount code, or a cheap product you refund afterwards.
- Check what arrives. You want exactly one browser Purchase and one server Purchase, with the same event ID, and the server one marked as deduplicated. Two browser purchases, two server purchases or two different IDs mean the problem is still there.
- Reload the order status page. Nothing new should arrive. If a second purchase does, it's firing on page view.
- Watch the ratio for a week. Meta purchases divided by Shopify online orders should settle near 1.0.
Once the counts are right, the next thing to improve is how well Meta can match those purchases to people, which is what Event Match Quality measures. Expect your reported ROAS to drop when duplicates disappear. Your real results haven't changed, you're just seeing them accurately, which is the point. Our guide to why Meta revenue doesn't match Shopify explains the gap that remains.
Common questions
What has to match for Meta to deduplicate pixel and Conversions API events?
The event name and the event ID. In the pixel, the ID goes in eventID, the fourth argument of the fbq track call. In the Conversions API, it goes in event_id. Both must be identical, and the events must arrive within 48 hours of the first one Meta received with that ID.
Which event does Meta keep when it deduplicates?
Meta generally keeps the event it received first and discards the later one, unless the two events differ in a meaningful way. That is why it does not matter much whether the browser or server event arrives first, as long as the IDs match.
Can I deduplicate with fbp or external_id instead of event_id?
Meta supports it, but it is weaker. It generally only works when the browser event arrives first and the server event follows, and it cannot catch duplicates from a single source. Use event_id for purchases.
Does Meta deduplicate two server events with the same event_id?
Meta discards repeats with the same event name and event ID within 48 hours, but do not rely on it to fix a setup that sends from two server sources. Remove the second source, because the two tools will rarely use the same ID.
What should I use as the event ID for a Shopify purchase?
Something both the browser and the server know about the same order. The Shopify order ID is the usual choice: it is available in the checkout_completed event in a custom pixel and in the order webhook on the server.
