Skip to main content

Meta Ads: segment to campaign to attribution

This guide walks the full advertising loop in Orbit from segment to attribution: connect a Meta Ad Account, activate a first-party CDP segment as a hashed Custom Audience, launch a click-to-chat campaign, and close the loop by forwarding conversion events back to Meta and reading attribution in Orbit. Each step links a capability page to the next, and this page covers the workflow; the Ads API reference has the endpoint schemas.

1. What the Ads surface contains

The Ads surface groups five capabilities under one account connection (base path /api/v1/ads; the public names below are customer-facing endpoints, the internal route module is only the implementation):
  • Connection — OAuth or token-based link to exactly one Meta Ad Account, encrypted at rest. The status endpoint reports the account currency so spend renders correctly.
  • Campaigns — create, list, update, and delete click-to-chat campaigns over WhatsApp, Messenger, or Instagram Direct. New campaigns are created PAUSED.
  • Conversions API — forward business outcomes (lead, purchase, registration, appointment) back to Meta for attribution, with email and phone SHA-256 hashed before dispatch.
  • Lead Ads ingestion — ingest a Meta leadgen webhook batch into tenant contacts through the same dedup and required-field machinery as a CSV import.
  • Custom Audiences — activate a CDP segment to Meta for targeting or suppression, hashed before upload.
Reads (status, campaign list, audience list) are open to any authenticated role; every write requires owner or admin.

2. Step 1 — build the audience segment

Decide who the ads should reach before connecting any spend. Build and refresh that segment in the CDP — the segment recipe is fully covered in CDP audiences: build segments. Two reminder points:
  • Base the segment on facts (attributes, events, computed traits) so a later sync references a segment_id, not a stale export.
  • Set auto_refresh on the segment so the Custom Audience re-sync below tracks membership without manual uploads.
Size the filter with POST /api/v1/contacts/segments/preview before saving, so a 0-match filter never reaches spend.

3. Step 2 — connect Meta and register the campaign surface

Connect one Meta Ad Account per organization, then verify:
Every other Ads endpoint returns CHANNEL_UNAVAILABLE until a connection exists. Have the OAuth flow (in-app callback) or a direct token ready, and have owner/admin on the API key used, because connect is a write.

4. Step 3 — activate the segment and launch the campaign

Activate the segment as a Custom Audience, then create the click-to-chat campaign:
Sequence matters: register the audience before the campaign, sync membership before the first spend, and only flip to ACTIVE after Meta review. A sync resolves up to 100,000 members per call and reports truncated: true when it cut the list — plan a second sync on larger segments. For the full campaign lifecycle (draft, dry-run, launch, holdout, ROAS), the campaign end-to-end guide carries the general pattern; this loop is the campaign-launch subset applied to ads.

5. Step 4 — read attribution back

Set the Conversions dataset_id once, post outcomes from the click-to-chat conversation into Meta, and pull the attribution view that joins spend, referrals, and outcomes:
Rules that keep the attribution honest:
  • Hashing before dispatch. Email and phone in user_data are SHA-256 hashed before they leave Orbit — raw PII never goes to Meta.
  • At least one identifier. A conversion without any of phone, email, external_id, or ctwa_clid cannot be matched by Meta and is rejected.
  • Test first. POST /api/v1/ads/conversions/test sends a test event to the dataset before live traffic, so a misconfigured dataset_id is caught cheap.
  • Read back cross-checked. The /ads/ctwa/attribution view joins captured click-to-chat entry points, orders, and ad spend into one report; reconcile it against the referrals funnel landing pages and the analytics funnel event log.

6. Cross-check the id and host boundaries

Three boundaries to keep clean when wiring this loop:
  • One account, one host. A single Meta Ad Account connects per organization, and the status endpoint returns the account currency — do not parse spend as USD if the connection says otherwise.
  • Membership caps. A Custom Audience sync is bounded to 100,000 members per call; the response marks truncated: true and a mode of add vs remove decides whether the segment sync adds members or suppresses them. Re-run sync against a paginated segment when it truncates.
  • Attribution is inbound-only. The click-to-chat referral stamp is recorded on inbound messages only, so the attribution funnel reads entry points, not outbound sends. Outbound delivery of ads never leaves the tenant — only hashed identifiers and hashed event IDs do.
Tenant-owned controls (per compliance) like consent check, suppression, and frequency caps apply to any segment you target or exclude, so pair the ads surface with opt-out lists when the segment feeds a suppression audience.

7. Troubleshooting

  • CHANNEL_UNAVAILABLE on every call. Connect first (POST /ads/connect), then re-read /ads/status.
  • 403 on a write. Reads are open to any role; writes need owner or admin — issue a key with the right role.
  • Conversions rejected. Verify dataset_id is set, at least one identifier is present in user_data, and run /conversions/test before posting live events.
  • Sync returns truncated: true. The segment exceeded the 100,000-member per-call window — split the segment or sync in successive paginated calls.
  • Attribution shows no entry points. The referral stamp lives on inbound click-to-chat messages only; check window_days (1–365, default 30) and that the campaign actually reached ACTIVE.

See also