Skip to main content

Ads attribution and lead ingestion

Orbit’s ads surface closes the advertising loop in two directions. The click-to-WhatsApp (CTWA) funnel captures which ad a customer tapped before a conversation starts, and credits downstream revenue back to that ad. The lead ads funnel ingests Meta instant-form submissions into your contact store. Together they let you answer “which ads bring customers in, and what are they worth” — the concept layer the ads activation guide and the ads console guide assume. Endpoint schemas live in the Ads API reference.

Two workflow halves

CTWA attribution covers the conversational channel: a customer taps a Facebook or Instagram ad that opens a WhatsApp, Messenger, or Instagram Direct chat. Meta stamps the first inbound message with a referral block — the source ad id, creative, and a ctwa_clid click id — and Orbit records it on the conversation. Lead ads ingestion covers the form channel: a prospect submits a Meta instant form, Meta sends a leadgen webhook, and Orbit turns the submitted answers into a contact with the originating ad attached. Both halves report into the same attribution view, and both forward outcomes back to Meta through the Conversions API so Meta’s delivery optimization sees the conversion too.

The CTWA walk, end to end

The attribution report (GET /ads/ctwa/attribution) rolls the funnel up over a configurable window — window_days from 1 to 365, 30 by default — and rolls up four layers per ad: raw referral counts, distinct first-touched contacts, attributed revenue joined with Meta spend, and conversions forwarded back to Meta. The window arithmetic is explicit: window_days=30 (the default) selects referrals and orders from the last 30 days and fetches Meta ad spend over the same UTC date span, so the ROAS numerator and denominator always cover the same period. Values of 1 and 365 are both accepted; outside integers are rejected with a 422.

A worked attribution walk

Suppose a 30-day window with two ads in play and a reporting currency of USD. Raw referral counts. Ad 12003451 (“Spring catalog”) carried 41 referral messages; ad 12001877 (“Retargeting”) carried 5. The second ad’s entries mostly came from re-entries — contacts whose first touch was ad 12003451 — so the entry counts read: unique_contacts counts each contact once, under their entry ad — which is why first-touch sums across ads never double-count a contact, and why the raw entry_count can exceed it. First-touched contacts and credited revenue. Of the 36 first-touched contacts on ad 12003451, eight placed a WhatsApp catalog order: twelve USD orders totaling USD 240.00, six EUR orders totaling EUR 120.00, and one AED order of AED 300.00. Any order placed before the ad tap is excluded from attribution — a contact’s history before their first touch cannot be credited to the ad. Revenue joined with spend to ROAS. With an FX rate of EUR→USD 0.92, the report normalizes the EUR slice to USD 110.40; the AED slice carries no supplied rate and is surfaced as unconvertible rather than blended at 1:1. Against USD 180.00 of Meta spend over the same window: ROAS is computed as attributed revenue over spend with the same maths Orbit’s campaign reports use, so ads and campaigns never disagree on the ratio. When an ad has zero attributed revenue, ROAS comes back null and the row carries an explicit cost status rather than a misleading 0. Conversions back to Meta. When your agent qualifies a lead or a purchase lands, POST /ads/conversions tells Meta which ad started the conversation. The match key is either the ctwa_clid click id stamped on the inbound message (sent un-hashed, as Meta issued it), or one or more of the contact’s identifiers — phone, email, external_id — each SHA-256 hashed before it leaves Orbit. At least one match key is required; a request with none returns 422. Raw PII never leaves Orbit.

Lead ads ingestion

Meta delivers a leadgen change carrying only identifiers: the lead id, plus the page, form, ad, and adgroup that produced it. Orbit fetches the actual answers from Meta’s Graph API and maps the submitted answers into a contact draft:
  • Known question names fold onto first-class contact columns — email, phone, first and last name, company, job title, city, state, country, and postal code, including Meta’s own aliases (phone vs phone_number, zip vs postal_code).
  • A full_name answer splits into first and last name when the form did not also ask them separately.
  • Every unrecognized question is preserved as a custom field, so no answer a prospect gave is silently discarded.
  • Attribution travels with the contact — the lead, form, ad, adgroup, and campaign ids land on the contact record so closed-loop reporting can join the contact back to the ad.
  • A lead with neither email nor phone is skipped, not stored — an empty contact record is worse than no record.
Ingestion runs through the same deduplication machinery as a contact CSV import, and each lead’s Meta lead id serves as its idempotency key — a retried webhook cannot create a duplicate, and a resubmission of the same form updates in place rather than creating a second contact.

What to branch on in your integration

These are the model’s fixed behaviors — branch on them rather than reverse-engineering the report:

Where ads events surface

  • The attribution report — per-ad entry counts, first-touched contacts, attributed revenue, spend, and ROAS, sorted best earners first.
  • CDP events — conversions posted to Meta are first modeled as outcomes in Orbit, so the analytics funnel and segment builders can filter on ad-driven entry points.
  • The Ads console — campaign insights (impressions, clicks, spend, started conversations) and the conversions configuration share the same connected account.
Because every ad event carries its source ids, joins across these views are deterministic — an attributed contact always resolves back to the ad and campaign that produced it.

Setup summary

  1. Connect one Meta ad account — OAuth from Settings > Channels; one account per organization. Writes need owner or admin. See the ads activation guide.
  2. Launch click-to-chat campaigns on WhatsApp, Messenger, or Instagram Direct from the Ads console. See the ads console guide.
  3. Subscribe the page webhook to leadgen so instant-form leads ingest into contacts.
  4. Set the Conversions dataset id and post outcomes with a click id or hashed phone/email match key, then read attribution back.

See also