13. Meta CAPI
The Conversions API via server GTM — redundant pixel+CAPI setup, event_id deduplication, the user_data match keys and hashing rules, and how to actually raise Event Match Quality.
For most advertisers, Meta is where server-side tagging pays its bill — and where it demands the most discipline. The payoff is Event Match Quality: post-ATT (Chapter 3), Meta's delivery optimization is starving for identity signals, and feeding it properly moves cost-per-result like few other levers. The discipline is deduplication: you will be sending the same events from two places at once, and Meta only forgives that if you follow its rules exactly.
What CAPI is
The Conversions API is Meta's server-to-server events endpoint: an
authenticated POST to graph.facebook.com/v…/{pixel_id}/events carrying
events in JSON. No browser involved — anything that can make an HTTP call can
send events: your backend, Shopify's native integration, a partner platform,
or — our route — a Meta CAPI tag in the server GTM container, fed by the
event stream you built in Chapters 10–12.
That last point keeps architectures honest: CAPI is not an sGTM feature. sGTM is one excellent way to produce CAPI events (it already has the events, the cookies, and the checkpoint privileges); a payment-webhook integration is another (Chapter 14). Many mature setups use both.
The architecture: redundant by design
Meta's recommended pattern is the redundant setup — keep the browser pixel and send CAPI:
┌── browser pixel ────▶ facebook.com/tr ───┐
one purchase ────┤ ├──▶ Meta dedup
└── server Meta tag ──▶ graph API (CAPI) ──┘ → ONE event
(fed by the GA4 stream into sGTM)Why send twice? Because each leg catches what the other drops. The pixel leg dies to ad blockers and ITP (Chapter 3) but carries browser-native signals when it survives; the CAPI leg always arrives, with richer identity, but only fires when your pipeline saw the event. Meta stitches the survivors into one deduplicated stream and takes the best of both.
(CAPI-only — dropping the pixel entirely — exists and works: cleaner privacy posture, zero Meta JavaScript on the page. You give up the browser-side signals and some product features; it's a deliberate trade chosen for privacy-first builds, not the default.)
Deduplication: the event_id contract
Meta's dedup rule, in one line: events with the same event_name +
event_id arriving on the same pixel within the dedup window (≈48 hours)
are treated as one — first arrival counts, later twins are discarded.
So both legs must carry the same ID for the same event instance. The clean implementation generates the ID once, upstream of both legs — in the data layer (Chapter 5), where every consumer can read it:
dataLayer.push({
event: "purchase",
event_id: "T-1024", // order ID — purchases name their own dedup key
ecommerce: { transaction_id: "T-1024", value: 138.0, currency: "EUR", … },
});
// non-transactional events: generate a UUID at push time- the web pixel tag sends it as the
eventIDparameter offbq('track'); - the GA4 web tag carries it as an event parameter into the server
(Chapter 12), where the server Meta tag maps it to
event_id.
This is why Chapter 7 told you to wire event_id into the data layer long
before CAPI was on the roadmap — both legs inherit it for free. The failure
smells are unmistakable: dedup broken = conversions roughly double
overnight (ROAS looks heroic, then the numbers stop reconciling anywhere);
one leg missing the ID = silent partial double-counting that only Events
Manager's breakdown reveals.
The payload: user_data is the product
A CAPI purchase, trimmed to its load-bearing fields:
{
"data": [{
"event_name": "Purchase",
"event_time": 1718200000,
"event_id": "T-1024",
"action_source": "website",
"event_source_url": "https://shop.example/checkout/thanks",
"user_data": {
"em": ["b4c9a2…(sha256 of normalized email)"],
"ph": ["8d969e…(sha256 of E.164 phone)"],
"fbp": "fb.1.1699….4567",
"fbc": "fb.1.1699….IwAR2x",
"client_ip_address": "203.0.113.7",
"client_user_agent": "Mozilla/5.0 …",
"external_id": ["sha256(U-1)"]
},
"custom_data": { "value": 138.0, "currency": "EUR" }
}]
}custom_data is the what; user_data is the who, and the who is the
whole game — these are the match keys Meta uses to resolve the event to a
person (Chapter 2's identity matching, formalized). Where each comes from in
an sGTM setup:
fbp/fbc— read from the first-party cookies that ride every request into your server (Chapters 11–12). This is a quiet superpower of the sGTM route: the cookies are just there, on the claimed request, no extra plumbing.em/ph/external_id— from the data layer at conversion time (logged-in user, checkout email) or attached server-side from your backend. Normalized, then SHA-256 hashed: lowercase/trim emails, digits-only international-format phones. Meta requires PII fields hashed; it compares against hashes of its own users' data.client_ip_address/client_user_agent— from the event model'sip_override/user_agent(Chapter 12). Not hashed. And yes — if the Chapter 11 proxy trap swallowed the real IP, you're sending your datacenter's address as a match key for every visitor.action_source+event_source_url— declare the channel honestly (websitefor web events); both required-in-practice for website traffic.event_time— the event's own timestamp (must be recent — within Meta's accepted window of about a week; clock skew or batch replays produce rejections).
Event Match Quality
Events Manager scores every CAPI event type 1–10 — EMQ — reflecting how many quality match keys arrived. It's not vanity: better matching means more conversions attributed, which means the bidding algorithm sees more wins, which means cheaper delivery. The practical ladder:
ip + user_agent only EMQ ~2–3 barely matching
+ fbp / fbc EMQ ~4–6 the cookie tier — table stakes
+ hashed email EMQ ~6–8 the big jump
+ phone / external_id / geo EMQ 8+ diminishing but real gainsAim for 7+ on revenue events. The two highest-leverage moves are almost
always: fix fbc capture (is the _fbc cookie actually being set on
ad-click landings? Chapter 7's base-pixel placement decides) and send the
checkout email hashed. Everything else is decoration by comparison.
The sGTM setup, concretely
Server container side:
- Add the Meta Conversions API tag (official template, or the widely-used Stape variant — both in the gallery).
- Pixel ID + access token — token generated in Events Manager → Settings → Conversions API. The token is a real secret, and here's Chapter 4's "a web container has no secrets" rule paying off: it lives in the server container, which never ships to a browser. (Finding a CAPI token in a web Custom HTML tag is an instant-rotate incident — and an audit classic.)
- Trigger on your mapped events — the templates auto-translate GA4 names
(
purchase→Purchase,generate_lead→Lead); review the mapping rather than trusting it.
Web side: nothing new — pixel with eventID, GA4 stream carrying
event_id, exactly as above.
Verification loop — Test Events (Events Manager): enter the
test_event_code in the server tag's test field, fire a test purchase, and
watch both legs arrive: each shows its source (Browser · Server), its match
keys, and whether dedup paired them. This is the Meta equivalent of
Chapter 12's server preview, and you don't go live without watching one
event arrive twice and count once.
Pitfalls, ranked by blast radius
| Pitfall | Symptom | Fix |
|---|---|---|
| Dedup broken (IDs differ/missing) | conversions ~double; ROAS inflated then trust collapses | one event_id minted in the data layer, consumed by both legs |
_fbc never captured |
EMQ stuck mid-range; paid attribution weak | base pixel on all pages, early (Ch. 7); check the cookie exists post-ad-click |
| Real IP lost to a proxy | every event matches on datacenter IP | Chapter 11: DNS-only on the tracking host; verify ip_override in preview |
| Hashing done wrong | EMQ low despite sending email | normalize then hash; lowercase, trim, E.164; SHA-256, hex |
| Access token in a web container | token public — rotate now | secrets live server-side, only |
Stale event_time (batch/replay) |
events rejected or misdated | send the event's own timestamp; respect the freshness window |
| CAPI fired for non-consented users | a privacy violation, not a bug | consent gates the server leg too — Chapter 17, no exceptions |
The shape of things
Step back and notice the recipe: two legs, one shared event ID, hashed PII as match keys, a platform-side quality score, a test console to verify dedup. That recipe is not Meta's — it's the industry's. TikTok's Events API, Google's enhanced conversions, LinkedIn's CAPI are the same dish with different seasoning, which is why Chapter 14 — More Destinations & Enrichment can cover three platforms and the webhook pattern in one chapter.