Meta's Conversions API is a single POST and a hash function — which is exactly why teams ship it broken and never notice. The envelope is simple; the dedupe key, the hashing rules, and the consent gate are where the signal quietly leaks.
This is the Meta half of the server-side conversion attribution stack: the request envelope, the canonical-to-Meta transformation, the PII hashing rules that differ from Google's, the Test Events verification surface, and the five failure modes that drag your dedupe rate below the 95% the pillar demands. It assumes the prerequisites are in place — canonical event schema, Worker fanout, consent propagation — and that you have a Meta Pixel ID provisioned in Events Manager.
The request envelope
One POST to https://graph.facebook.com/v19.0/{pixel_id}/events with a JSON body:
{
"data": [
{
"event_name": "Lead",
"event_time": 1716732738,
"action_source": "website",
"event_id": "crm-record-abc-123:lead-milestone",
"event_source_url": "https://nobrainermedia.com/contact/",
"user_data": {
"em": ["5e884898da28047151d0e56f8dc6292773603d0d6aabbdd62a11ef721d1542d8"],
"ph": ["5994471abb01112afcc18159f6cc74b4f511b99806da59b3caf5a9c173cacfc5"],
"fn": ["c1c224b03cd9bc7b6a86d77f5dace40191766c485cd55dc48caf9ac873335d6f"],
"client_ip_address": "203.0.113.42",
"client_user_agent": "Mozilla/5.0 ...",
"fbp": "fb.1.1716732738123.456789012",
"fbc": "fb.1.1716732738123.IwAR0..."
},
"custom_data": { "value": 7500.00, "currency": "USD", "order_id": "crm-record-abc-123" }
}
],
"test_event_code": "TEST12345"
}
Eight fields warrant operator attention:
event_namemaps your CRM milestone to Meta's vocabulary (Lead,Schedule,Subscribe,Purchase) or a custom name. Keep the mapping stable — an event that fires asLeadfor three months andlead_milestonethe fourth confuses the bidder.event_timeis Unix seconds (not milliseconds, not ISO 8601). Events older than 7 days are rejected by default.action_sourceiswebsitefor a web-traceable lead,system_generatedfor a purely CRM-internal milestone. The wrong value degrades the optimization signal.event_idis the dedupe primitive. With thefbp/fbccookies it lets Meta reconcile the server event against any legacy client-side pixel. Stable, unique per conversion, matched across both fires — this is the field that delivers the pillar's 95%+ dedupe rate.user_datacarries PII hashed per Meta's rules (below). Field codes (em,ph,fn) are Meta-specific; the Worker renamesemail → em,phone → phat the boundary.custom_data.value/currencydenominate closed revenue in ISO 4217 — multi-currency is involved enough that a dedicated spoke covers it.custom_data.order_idis the operator-side dedupe key (distinct fromevent_id), used to reconcile against a payment processor's own CAPI integration firing for the same order.test_event_coderoutes events to the Test Events tab instead of production attribution. Omit it in production.
PII hashing — where Meta differs from Google
Hash every PII field as lowercase, trimmed, hex-encoded SHA-256, each wrapped in an array (Meta requires ["…"], not "…"). Three places differ from Google Ads and bite teams that reuse one hash function for both:
- Phone. Digits only, no country-code prefix.
(415) 555-0100becomes4155550100— Google prefixes with+and country code, Meta does not. A transform that serves both platforms must hash phone twice. - State and country. Two-letter codes, lowercased:
canotcalifornia,usnot the full name. No abbreviation expansion. - Names. Lowercase and trim, but preserve hyphens and apostrophes — do not strip them.
The client_ip_address and client_user_agent stay unhashed; they lift match rates for users without fbp/fbc cookies and should always be populated when available.
Want CAPI wired with a dedupe rate that holds above 95%? Talk to the team that runs it. →
The canonical-to-Meta transformation
The transform is a pure function — per-field rename plus per-field hash, no I/O, no side effects — living in the Worker's /src/transforms/meta.ts. Pure functions are unit-testable, and the Meta envelope is among the most-tested modules in a production fanout layer:
function canonicalToMeta(
canonical: CanonicalEvent,
pixelId: string,
consent: ConsentState
): MetaCAPIEvent {
// returns the data[] element of the request body
}
Three transformations earn naming. event_name mapping is a static lookup table — new milestones get an explicit entry, never a string-similarity guess. Dedupe-key generation composes event_id as {record_id}:{milestone_name}, so a record moving lead → booked-call → signed-contract yields three independently-deduped IDs. Consent-to-LDU translation lives here, not in the ingestion layer, because consent rules are platform-specific.
The Test Events tool
Events Manager → Pixel → Test Events is the single highest-leverage verification surface in the whole stack — operators who skip it ship wire-ups that pass code review and silently fail in production. Generate a test code, attach it to test_event_code, fire from the Worker, and watch events land within ~5 seconds. Three gates fire there:
- Envelope validity — required fields present, types correct, event_time in window. Failures show as
partial_failureentries and red banners. - User-data match quality — a per-field status (green/yellow/red) plus a match-quality score. A low score means the hashing canonicalization is firing wrong.
- Dedupe registration — a successful dedupe shows both the server and client events sharing one
event_idand a single counted conversion.
The five failure modes
- event_id mismatch between server and client. Whitespace, casing, or format drift makes Meta count both. Generate the ID at ingestion and push it to the legacy pixel's
eventIDvia the data layer. - Wrong action_source.
system_generateddoesn't enrich the bidder likewebsitedoes. Source it from the CRM milestone, not a Worker default. - Hashing drift. Uppercase emails, phone-with-prefix, abbreviated states. Unit-test the canonicalizer against Meta's published examples.
- Missing fbp/fbc. Without them, matching falls back to user-data alone and match rate drops materially. Capture both at landing-page load and persist them through the CRM record.
- LDU mis-application. Fire it only for California traffic with withheld consent — gating on explicit consent plus geolocation, never as a default.
Closing
Meta CAPI is the most operationally subtle of the five platform wire-ups. The envelope is simple, the canonicalization is tractable, the failure modes are all explicit — what makes it subtle is propagating the event_id correctly between the server fire and any legacy pixel. Run the Test Events tool during week 3 of the build and you catch every failure category before cutover. Skip it and you find them three weeks later, when the CPA curve has drifted and the bidder has un-learned what it knew.
Ready to ship Meta CAPI correctly the first time? Book a 30-minute call →
Get the kit, not just the theory.
We'll send the build checklist behind this post — and the next pillar when it ships. One email, no drip sequence. Unsubscribe in one click.