> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Meta Ads: segment to campaign to attribution

> Connect a Meta Ad Account, activate a CDP segment as a Custom Audience, launch a click-to-chat campaign, and read attribution back through the Conversions API, analytics, and referrals.

# 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](/api-reference/ads) 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](/guides/cdp-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:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/ads/connect \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "access_token": "<meta-long-lived-token>", "ad_account_id": "act_12345" }'

curl https://api.orbit.devotel.io/api/v1/ads/status \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

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:

```bash theme={null}
# 1. Register the audience, linked to the segment
curl -X POST https://api.orbit.devotel.io/api/v1/ads/audiences \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "High-value recent clickers", "segment_id": "seg_high_value_clickers" }'

# 2. Sync membership (mode "add" or "remove" for suppression)
curl -X POST https://api.orbit.devotel.io/api/v1/ads/audiences/aud_abc/sync \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" -d '{ "mode": "add" }'

# 3. Create the campaign (starts PAUSED so you can review in Meta)
curl -X POST https://api.orbit.devotel.io/api/v1/ads/campaigns \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Promo — VIP click-to-WhatsApp", "destination": "WHATSAPP" }'

# 4. Activate when review passes
curl -X PUT https://api.orbit.devotel.io/api/v1/ads/campaigns/cmp_abc \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" -d '{ "status": "ACTIVE" }'
```

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](/guides/campaign-end-to-end) 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:

```bash theme={null}
# 1. Configure the dataset id once
curl -X PUT https://api.orbit.devotel.io/api/v1/ads/conversions/dataset \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" -d '{ "dataset_id": "<meta-dataset-id>" }'

# 2. Post a conversion (lead / purchase / registration / appointment)
curl -X POST https://api.orbit.devotel.io/api/v1/ads/conversions \
  -H "X-API-Key: dv_live_sk_your_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "event_name": "Purchase",
    "event_time": "2026-08-25T10:00:00Z",
    "user_data": { "phone": "+15551234567", "ctwa_clid": "<click-id>" },
    "custom_data": { "value": 49.99, "currency": "USD" }
  }'

# 3. Read the attribution funnel
curl "https://api.orbit.devotel.io/api/v1/ads/ctwa/attribution?window_days=30" \
  -H "X-API-Key: dv_live_sk_your_key_here"
```

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](/api-reference/referrals) landing pages and the [analytics funnel](/api-reference/analytics) 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](/compliance/send-gates)) like consent check, suppression, and frequency caps apply to any segment you target or exclude, so pair the ads surface with [opt-out lists](/guides/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

* [Ads API reference](/api-reference/ads) — every endpoint that backs this loop
* [CDP audiences: build segments](/guides/cdp-segments) — the segment recipe
* [Campaign end-to-end](/guides/campaign-end-to-end) — the general campaign lifecycle pattern
* [Analytics funnel](/api-reference/analytics) and [referrals funnel](/api-reference/referrals) — cross-check attribution
