focosys.io
← Writing
Essay · Tracking Infrastructure

Meta conversion tracking breaks quietly. Check the signal path.

Deduplication needs an exact event_id and event_name match between Pixel and CAPI. Here's where gateways and sGTM break that match without throwing an error.

Meta conversion tracking breaks quietly. Check the signal path.
Key takeaways
  • Deduplication depends on an exact match of event_id and event_name between the Pixel and CAPI request. A mismatched action_source or a missing param on one side breaks it silently, with no error thrown.
  • Gateway setups (Shopify, GTM server container, CDPs) are a common place this breaks, because the platform generates its own event_id server-side instead of reusing the client's.
  • Check Events Manager > Diagnostics, not just the Overview tab. The Overview tab can look clean while individual events show unmatched IDs in the raw event detail.
  • If browser and server counts summed together roughly equal your total event count, dedup isn't happening at all. It's just addition.

A client messages me: say Meta reports roughly 900 Purchases for the week while Shopify reports roughly 600. Ad spend hasn't changed. Nothing was launched. The Events Manager Overview tab shows Browser and Server both reporting healthy volume, plus a deduplication number that looks reasonable.

Except it isn't reasonable. It's just the two feeds added together with a fake dedup label slapped on top.

In my experience this is one of the most common CAPI failures I run into, right behind no dedup at all. It's worse than the first one because it looks fixed. Someone implemented event_id. It shows up in the payload. Everyone assumes the box is checked. Then a subset of events, say a small slice of traffic or sometimes a majority of it, never actually collapses.

What deduplication actually requires

Meta collapses two events into one only when a specific set of fields match between the Pixel event and the CAPI event:

  • event_name - identical string, case-sensitive
  • event_id - identical string, generated once and reused
  • event_time - within a reasonable window of each other (not exact, but close)
  • action_source on the CAPI side has to make sense given the Pixel event (usually website)

Miss any one of these and Meta treats the two events as unrelated. Not "probably related." Unrelated. There's no fuzzy matching, no fallback on email or IP. It's event_id + event_name or nothing.

{
  "event_name": "Purchase",
  "event_id": "3f29a-order-88213",
  "event_time": 1712000000,
  "action_source": "website",
  "user_data": { ... }
}
fbq('track', 'Purchase', {
  value: 149.99,
  currency: 'USD'
}, { eventID: '3f29a-order-88213' });

If the string in eventID on the Pixel call doesn't byte-for-byte match the event_id in the server payload, this is two events to Meta. Not a partial match. Two.

Case sensitivity kills this constantly

I've seen Purchase on the Pixel side and purchase on the server side because one team followed Meta's docs and the other followed their CDP's default event naming. In my experience Meta does not reliably normalize casing on event_name for dedup matching. Check both literally, character for character.

Where it breaks in practice

Where it breaks in practice

The gateway generates its own ID

This is, in my experience, the single most common cause, and it's specific to setups that route through something between the browser and Meta: Shopify's native Pixel + CAPI integration, a server-side GTM container, Segment, a CDP like mParticle.

The pattern: the browser fires the Pixel event with an eventID you control. Somewhere downstream, the gateway also sends a CAPI event to Meta, but it generates its own ID rather than reading the one attached to the browser event. Shopify's checkout events are notorious for this in my experience. The Pixel fires with one ID, Shopify's backend CAPI integration fires the same conversion with a UUID it generated internally.

Nobody configured this wrong on purpose. It's the default behavior of the integration, and the integration's docs usually don't mention it.

How to catch it: Open Events Manager > your Pixel > Diagnostics (not Overview). Click into individual Purchase events. Look at the raw event detail for both the browser and server instance of the same conversion. If the event_id values differ, that's your answer.

sGTM client and server containers disagree on the field name

If you're running server-side GTM, the web container has to push event_id into the dataLayer, the sGTM client has to read it, and the Meta CAPI tag template in the server container has to map it into the event_id field of the outgoing request. That's three handoffs.

The failure I see most: the dataLayer variable is named event_id, but the Meta CAPI tag in the server container is configured to read a parameter called eventId (camelCase) because someone copy-pasted from Pixel-side JS conventions where eventID is used. The tag builds a payload with a blank or missing event_id field. No error. The request still sends. Meta just receives a CAPI event with no ID and treats it as a standalone conversion.

Go into your server container, open the Meta CAPI tag, and look at the exact field mapping. Don't assume the tag auto-populates this. Several versions of the community template require an explicit variable reference. If you're debugging the container itself rather than the Meta tag, the same layered approach in debugging server-side GTM applies here.

action_source mismatch

action_source tells Meta where the CAPI event originated: website, app, phone_call, chat, etc. If your Pixel event is a standard website Purchase but your CAPI event is sent with action_source: "system_generated" or "other" because it came from a batch job or a backend order-processing script, Meta may not treat them as the same conversion path even with matching event_id.

This shows up in businesses that fire the CAPI event from an order confirmation webhook processed minutes or hours after the browser session ended, rather than from the same request flow. Set action_source to website whenever the underlying action originated from a site visit, even if the CAPI call itself fires from a backend job later.

Timing outside the window

Meta needs events to arrive within a reasonable window of each other to deduplicate them, and the closer together the better for matching reliability. I treat the matching window as a rough ceiling rather than a fixed guarantee, since exact thresholds can vary and I'm not citing a specific number here. If your CAPI event is a queued job that only processes overnight, and the Pixel event fired at the moment of purchase, that gap is usually still small enough to match. But I've seen delayed batch jobs (weekly reconciliation exports, for example) push events far enough past the original Pixel fire that Meta no longer reliably matches them. If your CAPI integration is not near-real-time, check the actual delay, not the assumed one, and confirm in Diagnostics whether matching is still happening at that delay.

Check the raw event, not the summary number

Events Manager Overview shows aggregated Browser, Server, and deduplicated counts. That view can look plausible even when a meaningful chunk of individual events aren't matching. Go to Diagnostics and inspect specific event instances. That's where the mismatch actually shows up.

The math that tells you it's broken

Quick sanity check before you go digging through payloads: pull Browser event count and Server event count for the same date range from Events Manager. Add them together.

If your reported total is close to that sum, dedup isn't happening. It's addition. A properly deduplicated total should be meaningfully lower than Browser-plus-Server, roughly equal to the count of unique conversions minus whatever the Pixel misses due to ad blockers and the CAPI misses due to failed matches.

If Total is roughly equal to Browser plus Server, stop looking at dashboards and start looking at individual event payloads. The dedup logic isn't partially working. It's not working.

What to actually check, in order

  1. Events Manager > Diagnostics. Pick a recent Purchase event. Open both the browser-side and server-side instance. Compare event_id character by character.
  2. Confirm event_name casing matches across the Pixel call and the CAPI payload.
  3. Check action_source on the CAPI request. Should be website for anything originating from a site session.
  4. Trace the event_id origin. Find where it's generated. It should be one value, created once, reused by both the Pixel call and the CAPI payload, with no regeneration anywhere in the pipeline.
  5. If you're on sGTM, open the Meta CAPI tag config directly and verify the field mapping for event_id is pointed at the correct dataLayer variable, not left on a default or blank.
  6. If you're on a gateway integration (Shopify, a CDP), check their documentation specifically for "deduplication" or "event_id passthrough." Several platforms require an explicit setting to reuse the client-side ID instead of generating their own.

None of this requires new tooling. It requires opening the raw event payloads instead of trusting the summary tab, which in my experience is exactly where I see people stop looking. If dedup itself isn't the issue and the events are matching but your match quality score is still low, that's a separate problem in the user_data fields, not the event_id logic covered here. The fix order for that is in debugging low Meta CAPI match quality.

focosys — Measurement systems writingMore writing →