First Party Data
Meta Event ID Deduplication: How the Pixel's eventID and the Conversions API's event_id Count One Purchase Once
Event ID deduplication is how Meta recognizes that a Meta Pixel event from the browser and a Conversions API event from your server describe the same action. When the pixel's eventID matches the server's event_id, the event names match and both reach the same pixel within 48 hours, Meta keeps one copy and discards the other. For a Shopify purchase, the ID both sides already know is the order ID.
For how the Conversions API itself works, start with our Meta CAPI and first-party data guide. This post goes one level down: the mechanism step by step with code, Meta's fallback method and its limits, which Shopify ID to use, how to check the result in Events Manager, sending Shopify orders through Zapier, and what deduplication can't fix. We checked every Meta, Shopify and Zapier detail against their own documentation on October 24, 2026.
Why does Meta need deduplication?
Meta recommends sending the same events from the browser pixel and from the Conversions API, a setup it calls redundant. The server copy covers events the browser loses to ad blockers, privacy settings and pages that never finish loading. The price is that every purchase reaches Meta twice.
Meta's guide to Conversions API deduplication says that advertisers with a redundant setup must give Meta a way to tell distinct events from overlapping ones. Without one, both copies count. An event you send from only one place, such as a Purchase sent only from the server, needs no deduplication.
The guide offers two methods: a shared event ID, which Meta recommends, and a fallback that compares the browser ID (fbp) or your own ID for the visitor (external_id).
How does event ID deduplication work, step by step?
1. Create one ID when the action happens. One purchase, one ID. Meta's server event parameters describe event_id as any unique string you choose and name an order number or transaction ID as good candidates.
2. Send it from the browser as eventID. It goes in the fourth argument of the pixel's track call:
fbq('track', 'Purchase', {value: 129.00, currency: 'USD'}, {eventID: '5871234567890'});
With several pixels on a page, fbq('trackSingle', 'PIXEL_ID', 'Purchase', {...}, {eventID: '...'}) sends to one of them with the same option, and the image pixel takes the ID as an eid URL parameter.
3. Send the same value from the server as event_id, with an event_name spelled exactly like the browser event. The body is a POST to https://, per Meta's Using the API page:
{
"data": [{
"event_name": "Purchase",
"event_time": 1792843200,
"event_id": "5871234567890",
"action_source": "website",
"event_source_url": "https://yourstore.com/",
"user_data": {
"em": ["62a14e44f765419d10fea99367361a727c12365e2520f32218d505ed9aa0f62f"],
"client_ip_address": "203.0.113.7",
"client_user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"fbp": "fb.1.1792842000000.1098115397"
},
"custom_data": {"value": 129.00, "currency": "USD"}
}]
}
4. Meta pairs the two. The pixel's eventID must equal the server's event_id, and the pixel's event name must equal event_name, for events sent to the same pixel ID.
5. The first copy wins for 48 hours. If the same ID and name reach the same pixel again within 48 hours of the first event with that ID, Meta discards the later copies. Meta's guide says it generally keeps the event it received first when the two don't differ meaningfully, and its parameter reference adds that when the browser and server copies arrive within five minutes of each other, it favors the browser copy.
| What has to match | Browser (Meta Pixel) | Server (Conversions API) |
|---|---|---|
| Deduplication key | eventID, the fourth fbq argument | event_id |
| Event name | 'Purchase' in the track call | "event_name": "Purchase" |
| Pixel | The ID in fbq('init') | The ID in the /events URL |
| Timing | Either copy can arrive first | Within 48 hours of the first copy |
Two consequences follow. Retries are safe: Meta's Using the API page says that when the same event_id and event_name arrive more than once, it keeps only the first copy, so resending a failed batch with the original IDs doesn't double count. Delays aren't: a server event's event_time can be up to seven days old, but if it reaches Meta more than 48 hours after the browser copy, the two aren't paired.
What is the fbp and external_id fallback, and where does it fail?
Meta's second method compares event_name plus fbp and/or external_id across the browser and server copies. fbp is the browser ID from the _fbp cookie; external_id is your own ID for the visitor, such as a user or loyalty ID, which Meta's customer information parameters page says to send in the same format everywhere. In the pixel, it goes into the init call:
fbq('init', 'PIXEL_ID', {external_id: 'CUSTOMER_ID'});
Meta's guide lists two limits:
- Browser first, then server. It generally works only when the browser event arrives first. A server event isn't discarded if no browser event arrived in the previous 48 hours, even when an identical browser event turns up afterwards.
- No deduplication within one source. Two consecutive server events with the same information are both kept, and so are two browser events.
| Event ID and event name | fbp or external_id | |
|---|---|---|
| Meta's status | Recommended | Fallback |
| Arrival order | Either | Browser first, then server |
| Two server copies of one event | Later copy discarded if the ID matches | Both kept |
| What you have to send | The same ID from both sides | The same fbp or external_id from both sides |
Our take. Give every event you send twice an event ID. Send fbp and external_id too, since they help matching, but don't count on them to catch duplicates.
Which event ID should a Shopify store use?
A good event ID passes three tests:
- Unique per action. Meta's example: two purchases with order numbers 123 and 456 each need their own ID, and the browser copies must carry the same numbers, so Meta sees two purchases, not four.
- Known to both sides at the moment each one sends.
- Identical as a string.
5871234567890andgid://shopify/Order/5871234567890don't match.
That rules out a customer ID or email (a customer who orders twice in two days would lose the second purchase, because Meta discards a repeated key within 48 hours), a timestamp (the browser and the server record different times) and any random value each side generates on its own.
For Purchase, use the order ID. In the browser, Shopify's checkout_completed event exposes it as event.data.checkout.order.id, and Shopify says that ID is null for every other event. On the server, the same order arrives through an order webhook or the Admin API. Shopify's REST Order resource shows a numeric id alongside admin_graphql_api_id: "gid://shopify/Order/…", and the GraphQL Order object has the number as legacyResourceId. Log what your browser code receives on a test order and make the server send exactly that string. The browser event also carries a checkout token, but the REST resource marks the order's checkout_token as deprecated, so the order ID is the safer key.
A Shopify custom pixel can send the browser copy. Shopify's custom pixel guide puts them under Settings > Customer events, says they're unsupported and need JavaScript knowledge, and tells you to remove existing pixel code first so events aren't counted twice. With Meta's base code loaded in the pixel:
analytics.subscribe('checkout_completed', (event) => {
const checkout = event.data.checkout;
fbq('track', 'Purchase', {
value: checkout.totalPrice?.amount,
currency: checkout.currencyCode,
}, {
eventID: String(checkout.order?.id),
});
});
Shopify says checkout_completed fires once per checkout, typically on the Thank you page, or on the first upsell page when you run post-purchase offers. If that page fails to load, it doesn't fire at all, which is the case the server copy exists for.
Events without an order have no natural ID. Meta's parameter reference says a random number works as long as the browser and server send the same one, so generate it in the browser, fire the pixel with it and pass it to your server with the event.
| Event | ID both sides can share | Browser gets it from | Server gets it from |
|---|---|---|---|
| Purchase | Order ID | checkout_completed: checkout.order.id | Order webhook or Admin API |
| AddToCart, InitiateCheckout, ViewContent, Lead | A random ID made in the browser | Generated when the event fires | Sent to your server with the event |
How do you check that deduplication is working?
- Place a real test order. Adding a
test_event_codelets you watch server events arrive in Meta's Test Events tool, but Meta says those events aren't dropped: they flow into Events Manager and are used for targeting and measurement. Either way, the order counts as real data, so cancel it in Shopify afterwards. - Read the browser copy's ID. In your browser's developer tools, filter network requests for
facebook.com/trand read theeidvalue; our Meta Pixel Helper guide shows where. Compare it with theevent_idin your server logs. They should be the same string. - Open the pixel's Overview in Events Manager. Meta's verification guide says it counts events before deduplication and shows the connection method for each event; Purchase should show both Browser and Server. Meta says events should appear within 20 minutes.
- Open Purchase, then Event Details, then the Event Deduplication tab. It shows the rate of events deduplicated from each source, with a warning when the rate is too low, and the rate of deduplication key usage. Overlap is the share of events whose key arrived from both sources, measured against the source with fewer events. Meta says low overlap means one or both sources send keys that aren't unique, or only one source sends a key.
- Compare with Shopify. Put Meta's purchases next to Shopify's orders for the same day, time zone and attribution window. If Meta still shows more, our guide to Meta reporting more purchases than Shopify covers the causes that aren't duplicates.
Because overlap is measured against the smaller source, a store whose server sends more purchases than the browser (as it should) can still show high overlap, as long as every browser purchase has a server copy with the same ID.
Can you send Shopify orders to the Conversions API through Zapier with an event ID?
Yes, if both sides can carry the same ID. Zapier's Facebook Conversions app has four actions (Send Purchase Event, Send Lead Event, Send Funnel Event and Send Other Event), and its Shopify and Facebook Conversions page includes a template that sends new paid Shopify orders as purchase events. Zapier's setup guide says you need full control of the business assets and must add Zapier as a partner to record events for a Meta Pixel.
Zapier's public pages don't list the action's input fields, so check them as you build the Zap:
- Map the Shopify order ID into the Send Purchase Event step's event ID field.
- Send the same order ID from the browser as
eventID, for example with the custom pixel above. If the browser Purchase comes from a pixel whose event ID you can't set, the Zap's events can't be paired with it, and Meta will usually count each order twice. - No event ID field? Use Webhooks by Zapier to POST the JSON body above to Meta's
/eventsendpoint yourself. - Watch the clock. Zapier labels each Shopify trigger as instant or polling, and polling triggers check for new data every 15 minutes on the Free plan. That fits inside 48 hours; a Zap that stalls for days doesn't.
Our take. A Zap is a workable stopgap for a store with no other server-side sender. If Shopify's Facebook & Instagram app is set to Enhanced or Maximum, it already sends purchases from Shopify's servers (what each level sends), and the Zap becomes a second server-side sender.
What can't event ID deduplication fix?
Deduplication can't reconcile senders that never agreed on an ID.
| Problem | Why Meta counts it wrong | Fix |
|---|---|---|
| Two server-side senders on one pixel, such as Shopify's app plus another tool, or a Conversions API Gateway left connected | Each tool creates its own IDs, and the fallback never pairs two server events | One server-side sender per pixel |
| Copies more than 48 hours apart | The window has closed | Send server events promptly and clear stalled queues |
| Different event names | Purchase and a custom name don't match | Use the same name on both sides |
| Copies sent to different pixels | Keys are matched per pixel ID | Send both copies to the same pixel |
| One ID reused across real actions | Meta discards a real later purchase | Use an ID unique to each action |
| Extra Purchase events that aren't copies, such as renewals | They aren't duplicates | Decide which purchases Meta should receive |
The first row is the one we find most often in audits. Our guide to Meta attribution pitfalls and event duplication walks through finding the extra source.
How does Upstack set event IDs?
With Upstack's default Browser + Server tracking method, Upstack loads Meta's pixel on your store and sends the same events from its servers, and both copies carry the same Upstack event ID, so Meta keeps one (Upstack's tracking method docs). For purchases, Upstack's JavaScript SDK derives the event ID from the order ID when you don't pass one (SDK reference). It can't pair events from another sender, so if Shopify's app or another tool also sends server events to the same pixel, turn that one off.
Frequently asked questions
Is event_id required in the Conversions API?
No. Meta marks it optional but recommends it for deduplication. Without it, or with different IDs on each side, a purchase sent from both the pixel and the server counts twice unless the fbp or external_id fallback catches it, and that only works when the browser copy arrives first.
Does Meta deduplicate two server events with the same event_id?
Yes. Meta's Using the API page says that when it receives the same event_id and event_name more than once, it keeps only the first copy. Two different tools rarely generate the same IDs, though, so this protects retries from one sender, not a second sender.
How long does Meta remember an event ID?
For deduplication, 48 hours from the first event it receives with that ID. A copy that arrives later is counted as a new event.
Can I use the Shopify order number as the event ID?
Meta suggests order numbers, and any order identifier works if the browser and server send it as the same string. On Shopify, the order ID is the one both the checkout_completed event and your server have.
See how Upstack Signal sends browser and server events with one shared event ID.
Want similar results?
Start free for 21 days and see what clean signal does for your performance. No card, no contract.