Server-side CAPI deduplication, explained
How Meta keeps one copy of a purchase: event_id matching, the 48-hour window, the fallback when IDs are missing, and how to verify it in Test Events.
Meta keeps exactly one copy of a conversion when your browser pixel and your server events agree on two things: the same event name and the same event_id, arriving within 48 hours of each other. Everything else — timestamps, page URLs, user parameters — helps Meta match events but does not, by itself, merge them. When the two sides disagree, Meta does not pick a winner: it counts both.
What has to line up on both sides
Four fields, and each one has a different job. The first is the merge key; the rest must simply be consistent.
| Field | Browser copy | Server copy | Merge role |
|---|---|---|---|
| event_id | Generated in the browser | Must reuse the browser's value | Primary key — identical values count once |
| event_name | purchase | purchase | Must match on both sides |
| action_source | website | website | Must match on both sides |
| event_source_url | location.href | Reconstructed from the payload | Send the same value — Meta uses it when pairing |
The server side never invents its own event_id. It reads the one the browser generated and stamps it on the server copy — that single decision is what makes deduplication work.
When the two layers disagree
If the IDs match, Meta keeps one purchase and discards the duplicate. If they don't, both events count: a store doing 200 purchases a week suddenly reports 400, ROAS reads double, and Meta's optimization trains on revenue that never happened. When the model corrects for the phantom volume, performance drops and the dashboard still looks green.
The usual suspects: a server library minting fresh IDs, an old integration still running next to a new one, or a theme snippet and an app both firing purchases with different IDs. See Meta pixel not firing on Shopify for the full list of ways a pixel setup rots.
The 48-hour window, and who wins a tie
Both copies must reach Meta within 48 hours of each other. Server retries after an outage, a queue that drains slowly, or a backfilled import can push the server copy past the window — each event then stands on its own and the purchase counts twice.
When both arrive at the same moment, Meta's tie-break favors the browser event. In practice that matters less than it sounds: the outcome is one counted conversion either way, as long as the IDs match.
If you don't send an event_id
Deduplication doesn't stop — it gets vaguer. Meta falls back to pairing events on shared identifiers (fbp, fbc) and consistent event parameters. The catch: the fallback only catches browser events that arrived before the server event, and any event sent twice from the server side can slip through and count again.
| Dedup path | With event_id | Without event_id |
|---|---|---|
| Matching rule | Shared event_name + event_id | fbp/fbc plus shared parameters |
| Window | 48 hours | Best effort |
| Server event arriving first | Still deduplicated | May count twice |
| Verdict | Default — always send one | Only when an ID is genuinely impossible |
How to verify it's working
Open Events Manager → Test Events, fire a test purchase, and look at the result row: if the browser and server copies show as one deduplicated event, you're done. If you see two separate events, the IDs don't match — fix that before the next dollar of ad spend, because right now every conversion is counting twice.
That verification step matters most right after you enable a new server-side tool. Meta's own guidance and every credible setup guide say the same thing: don't assume the pairing works — watch the first events arrive.
For what a broken setup costs you in practice, see Meta's free 1-click CAPI vs Headless.
Frequently asked questions
How do browser and server events deduplicate?
Meta keeps one copy when both events carry the same event name and the same event_id, and the two copies arrive within 48 hours of each other. When they land at almost the same moment, Meta prefers the browser event.
Can I deduplicate without an event_id?
Partially. Meta documents a fallback that pairs events on shared identifiers like fbp and fbc, but it only catches browser events that arrived before the server event. Without an event_id, delayed events and re-sends can count twice. Always send one.
What breaks deduplication?
Three things: the server side generating its own event_id instead of reusing the browser's, two tracking tools sending the same purchase with different IDs, and the two copies landing more than 48 hours apart. The symptom is doubled purchases in Events Manager and a ROAS that reads too high.