feat(tracking): Meta Pixel + Conversions API compliance overhaul - #89
Merged
Conversation
Browser pixel + server CAPI rebuilt to Meta's current spec, verified
live end-to-end (browser events, server Lead via durable Workflow,
Meta audit rows).
Client (cod-server/src/lib/capi.ts):
- Graph API v18.0 (expired) -> v26.0
- Network/5xx throw -> Cloudflare Workflow retries actually engage;
4xx returns success:false (Meta rejects whole batch - fail fast)
- PII per docs: phone normalized (+00213 forms fixed), city/names
diacritic-folded, fn/ln/external_id/event_source_url/content_ids added
- fbc/fbp/IP/UA unhashed, as Meta requires
Durability - the runtime cancels un-awaited promises after response,
which silently dropped every workflow trigger:
- All 3 Purchase trigger sites (dashboard status, Yalidine + ZR
webhooks) now run via executionCtx.waitUntil
- Lead moved from inline one-shot fetch to the same durable Workflow
path (capi-{orderId}-Lead), with event_source_url
Merchant control (migration 0018):
- conversion_event: Lead or Purchase - explicitly chosen by the
merchant, never defaulted; gates every send
- test_mode toggle routes CAPI to Meta's test stream
- ad_account_name reference label; access token now write-only
(masked reads, keep-on-empty save - same rule as dzverify/Sendili)
Checkout validation:
- Algerian mobile only (05/06/07 + 8 digits), server-enforced and
normalized to canonical local form; storefront blur-validation with
ar/fr/en messages
- InitiateCheckout fires on first checkout interaction, not form
visibility
Browser pixel:
- Custom deferred loader (dropped events when window.load stalled)
replaced with Meta's canonical base code
Audit + docs: capi_event_log rows for sent/failed/skipped incl. Lead;
docs/META-TRACKING.md end-user guide (trigger chain, webhook relation,
test mode, troubleshooting); README Growth Engine claims re-verified.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Complete compliance overhaul of the Meta Pixel + Conversions API integration, audited against Meta's current official docs, then verified live end-to-end (browser pixel events, server Lead through the durable Workflow, Meta audit rows in
capi_event_log).Full audit report:
report-md/facebook-pixel-capi-audit.md· End-user guide:docs/META-TRACKING.mdCAPI client correctness (
cod-server/src/lib/capi.ts)v18.0→v26.0— v18.0 was past CAPI's 2-year support window; Meta may fail or auto-upgrade expired-version callssendCapiEventswallowed HTTP failures); 4xx returnssuccess:false— Meta rejects the entire batch, retrying can't help00213…international prefix broke into a wrong 16-digit hash), city/names diacritic-folded and punctuation-stripped (Aïn Oussera→ainoussera), addedfn/ln/external_id(hashed) +event_source_url+content_ids;fbc/fbp/client_ip_address/client_user_agentcorrectly left unhashedDurability — the silent killer
Cloudflare Workers cancels un-awaited promises once the response is sent. Every CAPI trigger used
void create().catch()(and Lead was an inline one-shot fetch), so workflow creations were being cancelled before ever reaching the Workflows API — the "workflow never started" bug.executionCtx.waitUntil()capi-{orderId}-Lead) — retried, audit-logged, and gated by the merchant's conversion-event choiceMerchant control (migration
0018)conversion_event(Lead or Purchase): explicitly chosen by the merchant in the dashboard — the platform never decides; gates every server sendtest_mode: routes CAPI events to Meta's test stream (test_event_codeattached only while on)ad_account_name: merchant reference label (never sent to Meta)Checkout validation
05/06/07+ 8 digits), enforced server-side and normalized to canonical local form05XXXXXXXX(any of05…,+213…,213…,00213…accepted) — also stabilizes customer dedupInitiateCheckoutnow fires on the first checkout interaction (focus/keystroke/selection), not on the form scrolling into viewBrowser pixel
window.loaddeferral — dropped all events whenever load stalled or the script fetch raced) replaced with Meta's canonical base code, unmodifiedAudit + docs
capi_event_lognow records every outcome —sent/failed/skipped— including Lead attempts and skips (missing token, 7-day expiry)docs/META-TRACKING.md: end-user guide — setup, Lead-vs-Purchase tradeoffs, Test Mode walkthrough, the full trigger chain (incl. webhook relation: Yalidine/ZR webhooks auto-start Purchase at delivery; NOEST/EcoTrack start it on dashboard update), privacy/hashing, verification, troubleshooting, FAQTest plan
00213…, retry-classification, gate dispatch, workflow trigger viawaitUntil, cancellation-safety, masked-token contract)astro check+npm run validate(string/style validators + build) green0018applied to remote D1; columns verified viapragma_table_info12345,041…landline) rejected with Arabic error; E.164 phone stored canonical (+213 550 445 566→0550445566); order placement createdcapi-{orderId}-Leadworkflow → Meta accepted (audit rowsent+fbtrace_id); dashboard serves the new Tracking settings UIconversionEvent/testMode/accessTokenMaskedpresentNotes for reviewers
src/core/touch (normally do-not-modify):placeOrdernow forwards shopperUser-Agent/ IP /Referer, mirroring the existingcore/endpoints/abandoned.tspattern — without it, CAPI events carried the theme worker's identity instead of the shopper's (Meta requires the real IP for accurate matching)0018is additive-only (3 ALTERs onstore_pixel_config); already applied to remote D1out_for_deliveryfor far-southern wilayas keeps inside Meta's 7-day window; invalidcommuneIdon the raw API returns a 500 (storefront dropdown only offers valid communes — shoppers can't hit it)