Meta CAPI delivery correctness for CTWA + landing WhatsApp: per-country dataset/WABA repointing and dual event_name by action_source
The client's Meta WhatsApp conversions weren't optimizable and a naïve switch to standard events dead-lettered 316 events (subcode 2804066). Meta's business_messaging action_source accepts only a restricted vocabulary (LeadSubmitted, QualifiedLead, ViewContent…) and rejects 'Lead'; website Custom Conversions can't match messaging events at all. I repointed each country to its per-country business-messaging dataset with the correct WhatsApp Business Account (fixing 2804132), then designed a per-event dual event_name: business_messaging→LeadSubmitted/QualifiedLead, website→Lead/ViewContent, resolved by action_source (ctwa_clid vs fbclid+page_url). I added optional page_url passthrough on the backend source (after fixing a z.string().url() 500), made the change backwards compatible and gated it to the client, and verified a live CTWA event delivered as LeadSubmitted with HTTP 200, no more dead-letters. Landing campaigns can now optimize on Lead.
Reconciled a doc contradiction against Meta's official CAPI event list, then shipped a per-event routing model that unblocked landing-page optimization without touching another client.
Role: Architect of the routing model and diagnostician; deploy via paired Claude Code
The Problem
The client runs two Meta WhatsApp campaign types: native Click-to-WhatsApp (CTWA) and ads-to-landing-page-with-WhatsApp-button. Conversions weren't optimizable: website Custom Conversions stayed empty, and an attempt to switch to standard events dead-lettered ~316 events in 30 minutes, blocking campaign scaling.
Meta's business_messaging action_source only accepts a restricted event vocabulary (Purchase, LeadSubmitted, InitiateCheckout, AddToCart, ViewContent, ..., QualifiedLead) and rejects website events like 'Lead'/'ViewContent' with error code 100 subcode 2804066. Separately, an earlier failure was subcode 2804132 (dataset had no WhatsApp Business Account associated). Website Custom Conversions (Action Source: Website, rules URL/Referring Domain/Event Parameters) cannot match business_messaging events at all.
The two flows must send different event names AND different action_source from a single per-destination config; Meta's error messages were misleading; a third-party (Patagon) doc claimed 'Lead' works for CTWA while the official Meta CAPI doc and a live 2804066 said otherwise; and the fix had to be gated to one client without regressing other clients on the shared adapter.
Approach & Architecture
Repointed each country's Meta destination to its per-country business-messaging dataset with the correct WhatsApp Business Account, then verified against the official Meta CAPI-for-Business-Messaging doc that business_messaging accepts LeadSubmitted/QualifiedLead/ViewContent but NOT 'Lead'. Designed a backwards-compatible dual event_name: business_messaging uses event_name (LeadSubmitted/QualifiedLead), website uses a new optional event_name_web (Lead/ViewContent), resolved per event by action_source. Also added optional page_url passthrough on the backend source so landing leads carry event_source_url. Gated entirely to the client via presence of event_name_web.
metaAdapter.resolveActionSource: ctwa_clid → business_messaging (messaging_channel=whatsapp, whatsapp_business_account_id, ctwa_clid unhashed); fbclid+page_url → website (event_source_url required); else configured/default. metaAdapter.resolveEventName picks event_name (messaging) or event_name_web (website, fallback to event_name). Delivery gate (deliver.ts) skips Meta events older than 7 days and events with no match key.
Key Decisions & Trade-offs
Hardest Part
Resolving the direct contradiction between Patagon's doc ('use Lead for CTWA') and a live Meta 2804066 rejection. Pulled Meta's official Conversions API for Business Messaging doc, which lists the exact accepted event set, 'Lead' is not in it, but LeadSubmitted/QualifiedLead/ViewContent are, settling it authoritatively and reframing the whole approach.
Technical Detail
Per-destination Meta config gains event_name (messaging) + event_name_web (website); action_source resolved per event
Cloud Run gateway revs 00053/00056/00058; Supabase route_destinations config; Firestore dedup
325 gateway unit tests green; live probe (diag-ctwa.mjs) sending a real ctwa_clid lead and confirming Meta destination_event_name=LeadSubmitted, status ok, http 200 (no more 2804066)
Code
const actionSource = resolveActionSource(event, cfg); const eventName = actionSource === 'business_messaging' ? cfg.event_name // LeadSubmitted / QualifiedLead : (cfg.event_name_web ?? cfg.event_name); // Lead / ViewContent (website)
const PLATFORM_MAX_EVENT_AGE_SECONDS = { meta: 7246060 };
if (maxAgeSec && isOlderThan(event.event_time, maxAgeSec, now())) { return { retry:false, status:'skipped', isSuccess:false }; // not dead-lettered
}const lead = leads.find(l => l.ctwaClid);
const event = { source_event_name:'new_lead', event_id:lead.leadId+'diagctwa'+Date.now(), attribution:{ ctwa_clid: lead.ctwaClid } };
// expect submission.destinations[meta].destination_event_name === 'LeadSubmitted', status 'ok'Grounded the fix against primary sources instead of guessing: fetched Meta's official Conversions API for Business Messaging doc to get the authoritative accepted-event list, and Patagon's help doc to reconcile the contradiction. Read metaAdapter.ts/deliver.ts to design the resolveEventName change, wrote precise CC prompts to implement it backwards-compatibly, and ran a live CTWA probe to verify no 2804066.
Daily bridge now emits the correct per-flow Meta events without further changes (event names come from gateway config)
Measured Results
CTWA conversions deliver again as valid business-messaging events (LeadSubmitted/QualifiedLead, verified HTTP 200, no 2804066), and landing-page leads now also send website Lead/ViewContent that appear as selectable optimization events, unblocking the operator who could not switch an ad group off 'contact'. The dual-event change is gated to the client; a regression check confirmed no other client was affected.
| Metric | Value | Before | Source |
|---|---|---|---|
| Meta events dead-lettered by the invalid 'Lead' event (subcode 2804066) Caught and reverted before spreading to mx/ar/sql | 316 in 30 min (client_cl) | n/a | BigQuery dead_letter_events (reported by CC) |
| Gateway unit tests green after resolveEventName + page_url passthrough | 325 | n/a | CC test run |
| CTWA share of Chile traffic (leads with ctwa_clid) | 240 of 297 (24h) / 327 (15d) | n/a | Extraction counts |
| Meta re-send events after fix (client_cl/mx/ar) 0 ingest errors | 440 | n/a | Gateway ingest responses |
Every figure above was recorded during the work itself. Where no number was measured, none is claimed.