Skip to content
Tracking SOP

Offline Conversion Loop

Google via Data Manager, no code. LinkedIn + Meta via a purpose-built Cloudflare Worker — because CRM-triggered, delayed conversions need something GTM’s browser-session model structurally can’t do.

Google (via Data Manager, no custom code)

  • Two Import-type conversion actions: Qualified Lead (filter lead_status equals Good Prospect, value ₹1,550) and Converted Lead (filter lead_status equals Closed - Won, value ₹10,332) — both Secondary.
  • Connected via Data Manager’s direct Google Sheets connector, reading the live leads Sheet on a daily schedule.
  • Field mapping: timestamp → Conversion date/time, lead_id → Transaction ID, Event source → static "Web", gclid → GCLID, email/phone → Email/Phone (hashed automatically by Google).

LinkedIn + Meta (via a custom Cloudflare Worker)

  • Built because neither platform’s no-code tools support CRM-triggered, delayed conversion events the way Google’s Data Manager does — GTM-based tags only fire on live browser sessions, which doesn’t apply to a lead status changing days later in a spreadsheet.
  • A single scheduled Worker (Cron Trigger, every 30-60 min) reads the leads Google Sheet via service account JWT authentication, and for qualifying rows not yet marked pushed, calls LinkedIn’s Conversions API and Meta’s Conversions API independently, updating linkedin_pushed / meta_pushed flags only on genuine per-platform success.
  • LinkedIn: Direct API access (token via Campaign Manager → Signals Manager → Direct API, no developer app approval needed, tokens don’t expire). Two Conversion actions: Offline - Qualified Lead (API) (ID 30633177) and Offline - Closed Won (API) (ID 30633185). Confirmed live in the deployed Worker source — both IDs match exactly.
  • LinkedIn payload requires user.userIds to always be present, even as an empty array, or LinkedIn returns a hard 422 (Debugging Journal #10). Confirmed in the Worker: rows with zero identifiers are now cleanly pre-filtered and skipped before any API call, never counted as a failure (Debugging Journal #12).
  • Meta: reuses the existing CAPI access token and dataset ID, action_source: "system_generated" (not "website", since there’s no live browser session).
  • A Google Sheets tab name containing an em dash and spaces (Digital Asur — Leads) required wrapping in single quotes for every A1-notation range reference — confirmed live in the Worker source via a dedicated sheetRange() helper that applies this automatically (Debugging Journal #11).
  • Deployed entirely via the Cloudflare dashboard — no CLI/wrangler used anywhere in this build.
  • A lightweight self-check scans every LinkedIn/Meta API response for deprecation signals and sends a rate-limited email alert (via the same Brevo integration used for lead-verification email) the moment one appears — the actual, primary defense against API version drift, not manual periodic checking.