- Meta deduplicates a browser (Pixel) event and a server (Conversions API) event when the Pixel’s
eventIDequals the server’sevent_idand the event names match. - Events are only deduplicated if they are received within 48 hours of the first event with that ID, and Meta generally keeps the event it received first.
- The fallback with
fbpand/orexternal_idonly works when the browser event arrives first. Treat it as a safety net, not a plan. - On WordPress, the usual culprits are cached pages serving a stale ID, IDs generated separately in browser and server, mismatched event names and two plugins firing the same event.
- Use the order ID for purchases and a UUID created once and shared for everything else. Verify in Events Manager (Test Events and the Event Deduplication tab).
You added the Conversions API (CAPI) to recover events the browser Pixel misses. Now Events Manager shows twice the purchases your shop actually took. Nothing is broken in the sense that errors appear; Meta is simply doing what you told it to, which is count two events as two events.
Deduplication is the mechanism that tells Meta “these two events are the same purchase”. It is simple in principle and easy to break in practice, especially on WordPress where caching, plugins and themes all get a say. This guide explains the rules from Meta’s own documentation, shows where they fail, and gives you a way to verify the result.
What deduplication is, and why you need it
Meta recommends running the Meta Pixel and the Conversions API together and sharing the same events through both, so that if one channel misses an event the other can supply it (Meta, Conversions API best practices). That redundancy creates a side effect: when both channels succeed, Meta receives the same purchase twice.
Deduplication is the step where Meta recognises the pair and keeps one. Without it, your reported conversions, cost per result and ROAS are inflated. With it, you get the benefit of redundancy: the Pixel covers what the server missed, the server covers what the browser missed (ad blockers, browser restrictions, closed tabs), and overlapping events are counted once. For background on why browsers lose events in the first place, see Safari ITP and WordPress cookies and why your WordPress analytics data is missing.
The matching rules, exactly
Meta’s deduplication documentation describes the recommended method: in corresponding events, the Pixel’s eventID must match the Conversions API’s event_id, and the event name must match too (Meta, Deduplicate Pixel and Server Events). In practice that gives you three checks:
-
Same ID. Pixel
eventIDequals serverevent_id, character for character. -
Same event name. The Pixel event (for example
Purchase) equals the serverevent_name. - In time, and to the same Pixel. The second event must be received within 48 hours of the first (Meta’s rule). Send both to the same Pixel (dataset) ID; Meta’s pages do not spell this out, but the endpoint is per Pixel, so mixed IDs cannot pair up.
The word generally matters for the winner. Meta states that when duplicates occur it generally prefers the event that was received first. Do not build logic that depends on the server version “overriding” the browser version, because that is not how it works.
eventID versus event_id: same thing, two spellings
This trips people up more than it should. The Pixel takes a camelCase eventID in an options object, the fourth argument of fbq. The Conversions API takes a snake_case event_id inside the event. They refer to the same concept and must hold the same value. The image tag form of the Pixel uses eid. Meta’s documentation shows all three forms.
The 48-hour window and which event survives
Per Meta, events are only deduplicated if they are received within 48 hours of when Meta receives the first event with a given event_id. The clock starts at the first event, not at the order. Normally the browser event lands within seconds and the server event within seconds to minutes, so the window is rarely the problem. It becomes one when a server queue stalls, a failed delivery is retried days later, or you replay historical events.
The fallback: fbp and external_id
If you cannot send an event ID, Meta describes a second method: matching on the event name plus the fbp and/or external_id parameters. The documentation is blunt about its limits: it only works for deduplicating events sent first from the browser and then through the server, and it does not deduplicate events when only one event source is used.
That makes it weaker for three reasons:
- Order dependent. If the server event arrives first, which happens easily with a fast server-side forwarder and a slow page, the fallback does not help.
-
Parameter dependent. It needs
fbp(the browser identifier stored by the Pixel) and/or your ownexternal_idon both sides. If a cookie is missing or blocked, there is nothing to match. -
Less explicit. An event ID says “these two are the same purchase”.
fbpsays “these came from the same browser”, which is a looser claim.
Meta’s best practices recommend including both: use identical event names and either event_id or a combination of external_id and fbp, and send external_id and event_id for all events. Use event_id as the primary key and the other fields as a backup.
Matched or not matched: the truth table
Read it as a quick lookup. If the IDs differ, nothing matches by ID. If the names differ (Purchase versus purchase, or a custom OrderComplete against the standard Purchase), nothing matches even though the IDs are identical. Treat names as exact strings.
Code: Pixel and Conversions API with a shared ID
The browser side. This is the form shown in Meta’s documentation, with the ID passed in the fourth argument:
fbq('track', 'Purchase',
{ value: 49.00, currency: 'USD' },
{ eventID: 'order-1042' }
);
The server side. The endpoint format is https://graph.facebook.com/{API_VERSION}/{PIXEL_ID}/events with an access token, and each event carries its own event_id (Meta, Using the API). The required fields are event_name, event_time, action_source, user_data and event_source_url:
{
"data": [{
"event_name": "Purchase",
"event_time": 1790000000,
"event_id": "order-1042",
"action_source": "website",
"event_source_url": "https://example.com/checkout/thank-you/",
"user_data": {
"em": ["<sha256-hashed-email>"],
"client_ip_address": "203.0.113.7",
"client_user_agent": "Mozilla/5.0 ..."
},
"custom_data": { "value": 49.00, "currency": "USD" }
}]
}
The ID value is the only thing the two snippets must share. How you produce it is the real design question, covered below. For the full request anatomy see our complete guide to the Meta Conversions API for WordPress, and for what else belongs in user_data, Event Match Quality explained.
Why deduplication fails on WordPress
WordPress adds failure modes that a plain app does not have. These are the ones that come up repeatedly.
1. Page caching serves a stale event ID
If your server renders the ID into the page HTML (a PHP-generated UUID inside the Pixel snippet, say) and a page cache stores that HTML, every visitor receives the same ID. The first purchase is deduplicated correctly; later ones send a browser event with an ID Meta has already seen, or one that matches no server event. Thank-you pages are often excluded from caching, but not always, and fragment caches and CDNs can undo that. If an ID must change per event, generate it in the browser at event time or fetch it uncached, or derive it from something that is genuinely unique per request, such as the order ID.
2. Different ID generation in browser and server
A frequent pattern: JavaScript creates Math.random() IDs for the Pixel while PHP creates its own for CAPI. Each side is individually valid and they never match. IDs must be created once, in one place, and shared. See the strategy section.
3. Event names that differ
One tool sends Purchase, another sends purchase or a custom name such as woo_purchase. The IDs match; the names do not; nothing is deduplicated. This is also common when a form plugin maps “submitted” to Lead in the browser but CompleteRegistration on the server.
4. Double-firing plugins
Two plugins that each install a Pixel (for instance a theme option plus a marketing plugin) send the same event twice from the browser, possibly with different IDs. Deduplication cannot fix that: you now have two browser events and one server event. Remove the extra sender first.
5. Events outside the window or to a different Pixel
Failed server deliveries retried the next week and mixed Pixel IDs between environments (staging Pixel in the browser, production Pixel on the server) both look like “deduplication is broken” and are not about IDs at all.
For caching in particular, see also client-side versus server-side tracking in WordPress.
A recommended ID strategy
-
Purchases: use the order ID. It exists on both sides, is unique, and is stable if a page reloads or a webhook retries. Prefix it if you share a Pixel across stores (
shopA-1042). Reloading the thank-you page then sends the same ID again rather than a new one. - Everything else: a UUID created once and shared. For leads, add-to-cart, page views and form submissions, generate one UUID at the moment of the action, and hand the same value to both the Pixel call and the server event. Do not generate it twice.
- One ID per distinct event. Two different purchases must never share an ID; one purchase must never have two.
- Keep the name map in one place. Standard event names, spelled once, used by both paths.
A short UUID snippet for the browser, which then passes the same value to your server endpoint:
const eventId = crypto.randomUUID();
fbq('track', 'Lead', {}, { eventID: eventId });
// send the same eventId with your form request so the server reuses it
How to verify in Events Manager
Do not trust a setup because it “looks right” in the code. Check it.
-
Use Test Events. Meta’s Test Events tool in Events Manager generates a test ID that you add to server payloads as
test_event_code; remove it before sending production traffic (Meta, Using the API). Trigger a real action in the browser and confirm you see both the browser and the server event with the same ID. - Open the Event Deduplication tab. In Events Manager, select your Pixel, open Event Details for an event type and go to the Event Deduplication tab. It shows the rate of events deduplicated per source and the rate of deduplication key usage, including the overlap, which is the share of events with a given key received from both sources (Meta, Verifying your setup).
-
Read the overlap. Meta notes that low overlap indicates non-unique keys or keys sent from only one source. Low overlap on
event_idusually means one side does not send it, or the values differ. - Compare with real orders. Count orders in WooCommerce for a day and compare with the Purchase count in Events Manager. A count near double is the classic duplicate signature. Attribution differences will move the numbers somewhat; double is not an attribution effect.
Where Traculo fits
Traculo’s WordPress plugin forwards events from its servers to Meta Conversions API and other platforms, and keeps a delivery log with the response from each integration, so you can inspect what was sent for a given event. It does not replace the checks above: the Pixel in the browser still needs to use the same ID and event name, and you should verify that in Events Manager. Traculo sends the stored event’s numeric ID as event_id, so a browser Pixel deduplicates against it only if it uses that same value. If you are weighing options, start with server-side tracking without GTM.
Frequently asked questions
Do I need event_id if I use fbp and external_id?
Not strictly, but Meta recommends event_id and includes the fbp/external_id combination as a fallback. The fallback only works when the browser event arrives first, so event_id is the more reliable choice.
Which event does Meta keep, browser or server?
Generally the one it receives first, per Meta’s documentation. It is not “server always wins”.
What happens after 48 hours?
Events are only deduplicated if received within 48 hours of the first event with that ID. A later duplicate is not recognised as one.
Is eventID the same as event_id?
Yes. eventID is the Pixel’s option name, event_id is the Conversions API field. They need the same value.
Will deduplication fix two plugins both firing the Pixel?
Not reliably. If both browser events carry different IDs, you still have two browser events. Remove the extra sender.
Related reading
- Meta Conversions API for WordPress: the complete guide
- Event Match Quality: what the score means and how to raise it
- Dedup rules and limits on every ad platform
- TikTok, Snapchat and Pinterest Conversions API on WooCommerce
- Safari ITP and WordPress cookies
- GA4 server-side tracking for WordPress
- Server-side tracking for WordPress: a beginner’s guide
Want to see exactly what was forwarded to Meta for each event? Take a look at Traculo’s server-side event tracking.