Skip to content

feat(tracking): Meta Pixel + Conversions API compliance overhaul - #89

Merged
bighadj22 merged 1 commit into
mainfrom
feat/meta-pixel-capi-compliance
Sep 4, 2026
Merged

feat(tracking): Meta Pixel + Conversions API compliance overhaul#89
bighadj22 merged 1 commit into
mainfrom
feat/meta-pixel-capi-compliance

Conversation

@bighadj22

Copy link
Copy Markdown
Owner

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.md

CAPI client correctness (cod-server/src/lib/capi.ts)

  • Graph API v18.0v26.0 — v18.0 was past CAPI's 2-year support window; Meta may fail or auto-upgrade expired-version calls
  • Retry model fixed: network errors and Meta 5xx now throw, so the Workflow's 5-attempt exponential-backoff retry actually engages (it previously only retried thrown errors while sendCapiEvent swallowed HTTP failures); 4xx returns success:false — Meta rejects the entire batch, retrying can't help
  • PII per docs: phone normalization fixed (00213… international prefix broke into a wrong 16-digit hash), city/names diacritic-folded and punctuation-stripped (Aïn Ousseraainoussera), added fn/ln/external_id (hashed) + event_source_url + content_ids; fbc/fbp/client_ip_address/client_user_agent correctly left unhashed

Durability — 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.

  • All three Purchase trigger sites (dashboard status transition, Yalidine webhook, ZR Express webhook) now run via executionCtx.waitUntil()
  • Lead moved onto the same durable Workflow path (capi-{orderId}-Lead) — retried, audit-logged, and gated by the merchant's conversion-event choice

Merchant control (migration 0018)

  • conversion_event (Lead or Purchase): explicitly chosen by the merchant in the dashboard — the platform never decides; gates every server send
  • test_mode: routes CAPI events to Meta's test stream (test_event_code attached only while on)
  • ad_account_name: merchant reference label (never sent to Meta)
  • Access token is now write-only: API returns a masked hint, empty-on-save keeps the stored token (same rule as the dzverify/Sendili keys)

Checkout validation

  • Algerian mobile only (05/06/07 + 8 digits), enforced server-side and normalized to canonical local form 05XXXXXXXX (any of 05…, +213…, 213…, 00213… accepted) — also stabilizes customer dedup
  • Storefront blur-validation with ar/fr/en messages; abandonment tracker aligned
  • InitiateCheckout now fires on the first checkout interaction (focus/keystroke/selection), not on the form scrolling into view

Browser pixel

  • The custom deferred loader (hand-rolled queue + window.load deferral — dropped all events whenever load stalled or the script fetch raced) replaced with Meta's canonical base code, unmodified

Audit + docs

  • capi_event_log now 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, FAQ
  • README Growth Engine section rewritten — every claim code-verified (old text wrongly claimed IP/UA were "hashed"; they must NOT be)

Test plan

  • cod-server: 113 files / 1664 tests green (73 new: fixed SHA-256 hashing vectors, phone normalization table incl. 00213…, retry-classification, gate dispatch, workflow trigger via waitUntil, cancellation-safety, masked-token contract)
  • cod-client-astro: typecheck + 142 tests green; i18n three-locale parity (guard test)
  • theme01: 10 tests + astro check + npm run validate (string/style validators + build) green
  • Migration 0018 applied to remote D1; columns verified via pragma_table_info
  • Live verification on production: canonical pixel snippet rendering with merchant's pixel ID; invalid phones (12345, 041… landline) rejected with Arabic error; E.164 phone stored canonical (+213 550 445 5660550445566); order placement created capi-{orderId}-Lead workflow → Meta accepted (audit row sent + fbtrace_id); dashboard serves the new Tracking settings UI
  • OpenAPI spec live: conversionEvent / testMode / accessTokenMasked present

Notes for reviewers

  • One deliberate theme01 src/core/ touch (normally do-not-modify): placeOrder now forwards shopper User-Agent / IP / Referer, mirroring the existing core/endpoints/abandoned.ts pattern — without it, CAPI events carried the theme worker's identity instead of the shopper's (Meta requires the real IP for accurate matching)
  • Migration 0018 is additive-only (3 ALTERs on store_pixel_config); already applied to remote D1
  • Known tradeoffs (unchanged, documented): Purchase at out_for_delivery for far-southern wilayas keeps inside Meta's 7-day window; invalid communeId on the raw API returns a 500 (storefront dropdown only offers valid communes — shoppers can't hit it)

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.
@bighadj22
bighadj22 merged commit 725d137 into main Sep 4, 2026
3 checks passed
@bighadj22
bighadj22 deleted the feat/meta-pixel-capi-compliance branch September 5, 2026 01:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant