Skip to content

Google attribution parity (server-side conversions + gclid rescue)

Status: built 2026-07-08 (branch worktree-fix-google-attribution-gaps); live once the Data Manager API is enabled on the GCP project and env vars are set (see "Activation" below). Owner: Aaron Related: investigation.md (API landscape + quantified gap) · marketing-scorecard-attribution · docs/engineering/infrastructure/analytics.md (canonical as-built reference)

Problem

Meta had three routes into conversion_attributions (first-touch fbclid rescue, Instant-Form lead match, CAPI server events); Google had one (browser gtag + on-site UTM/gclid capture). The June 2026 cross-check (scripts/marketing/cross-check-google-attribution.ts) showed Google Ads claiming 27 purchases while only 5 were server-verifiable.

What was built

  1. gclid first-touch rescueUser.gclidFirstTouch(+At), written first-touch-only by POST /api/user/attribution (signup/login) and the tour stub-user path; user-gclid-rescue branch in AttributionService carry-forward. Precedence: prior → meta-lead → fbclid → gclid → cm-reported.
  2. Server-side conversion uploadlib/google-ads/{data-manager-client,hash-identity,track-purchase,track-lead}.ts, a file-for-file mirror of lib/meta/. Fires Purchase beside every Meta CAPI Purchase (7 sites) and Lead beside every Meta CAPI Lead (6 sites), consent-gated identically. Dedup vs the browser tag via shared transactionId (browser gtag transaction_id fixed from '' to the Meta event id).
  3. No Google lead ingestion — audited 2026-07-08: no Google Lead Form assets exist in any account under the MCC. No GoogleLead table built (deliberate).
  4. AdLeadDaily google rowssync-ad-data cron now pulls per-campaign/day SUBMIT_LEAD_FORM conversions for platform='google'.

Account state (created/verified via scripts/marketing/google-ads-conversion-setup.ts)

  • Conversion-tracking owner: customer 6392867894 (tracking id 17907719909); customer data terms accepted.
  • Purchase action 7510162937 (existing gtag WEBPAGE action — uploads target it so transactionId dedup works).
  • Lead action 7677760314 "Lead (server upload)" (UPLOAD_CLICKS, SUBMIT_LEAD_FORM, secondary — promote to primary only as a deliberate bidding decision).
  • enhanced_conversions_for_leads_enabled is still false (UI toggle); user-data-only lead matching may not attribute until enabled. gclid-keyed events unaffected.

Activation checklist (blocked on Aaron)

  1. Enable the Data Manager API on the service-account GCP project: gcloud services enable datamanager.googleapis.com --project=955034515062
  2. Run the validateOnly round-trip: cd apps/api && npx tsx scripts/marketing/google-ads-conversion-setup.ts validate-dm --operating-customer=6392867894 --action-id=7510162937
  3. Set Vercel prod env: GOOGLE_ADS_CONVERSION_CUSTOMER_ID=6392867894, GOOGLE_ADS_CONVERSION_ACTION_ID_PURCHASE=7510162937, GOOGLE_ADS_CONVERSION_ACTION_ID_LEAD=7677760314 (code silently skips until set).
  4. Optional: enable enhanced conversions for leads in Google Ads UI (Settings → Enhanced conversions) for user-data-only lead matching.

Known limits / deferred

  • gbraid/wbraid captured end-to-end (added 2026-07-08 after the initial build): UTM_PARAMSattributionSchema → Stripe metadata → conversion_attributions columns (migration 20260708130034) → DM adIdentifiers, and both count as real ad attribution. Deliberately NOT persisted as User first-touch columns (no braid analog of gclidFirstTouch) — braids only survive the 24h sessionStorage window unless the conversion lands in-session; revisit once conversion_attributions shows real braid volume.
  • Meta's InitiateCheckout / CompleteRegistration have no Google counterpart — only Purchase + Lead are mirrored (deliberate: those are the biddable Google actions; mid-funnel Google actions would be unused).
  • Vanity conversions are still primary-for-goal in the account (YouTube follow-on/subscriptions, Local actions, Store visits) — bidding-noise cleanup is a marketing decision, flagged not changed.
  • fulfillOrganizationDedicatedRoom (B2B path) fires no conversion events, same as Meta.