> ## 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.

# Read the click-to-chat attribution funnel

> Interpret the CTWA attribution funnel — entry points, customers, revenue, and ROAS per ad — including the window bounds, the response fields, and how to reconcile it against referrals and analytics.

# Read the click-to-chat attribution funnel

`GET /api/v1/ads/ctwa/attribution` is the reading side of the click-to-chat (CTWA) loop. Setup — connecting Meta, launching a click-to-chat campaign, stamping the referral on inbound — lives in the [ads activation guide](/guides/ads-activation) and the [QR guide](/guides/qr-codes). This page covers **how to interpret** the funnel once data is flowing: what it joins, what each field means, and how to reconcile it.

For request/response schemas, see the [Ads API reference](/api-reference/ads).

## 1. What the funnel joins

The funnel rolls up three sources by the ad that drove them:

* **Entry points** — every inbound message stamped with a Click-to-WhatsApp referral (`source_id` = the ad id). Because the stamp lives on inbound only, the funnel reads *entries into chat*, never outbound sends.
* **Orders** — WhatsApp catalog orders from those contacts, credited **first-touch** to the ad that opened their chat. So the funnel attributes "which ad acquired this customer," not "which ad last touched them."
* **Ad spend** — Meta ad spend for the same window, fetched over one batched ad-level insights call, so the ROAS numerator and denominator align.

This is why the funnel closes the acquisition loop: it answers "which ads brought customers in, how many, how much revenue they generated, and the return on that spend."

## 2. Source metrics

| Field                                           | Meaning                                                                                                                                                                 |
| ----------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `entry_count`                                   | Raw inbound messages carrying this ad's referral — touch volume. Can exceed `unique_contacts` when the same customer re-enters via the same ad.                         |
| `unique_contacts`                               | Distinct contacts whose **first** CTWA touch was this ad. Each contact counts once under their entry ad, so per-ad counts never double-count.                           |
| `first_seen` / `last_seen`                      | Inbound click timestamps bounding this ad's referral traffic inside the window.                                                                                         |
| `attributed_revenue_cents`                      | Revenue credited to this ad (first-touch over the contact's orders), converted into the reporting currency.                                                             |
| `unconvertible_revenue_cents`                   | Revenue in a currency with no exchange rate — surfaced, not silently blended at 1:1.                                                                                    |
| `spend_cents`, `roas`, `roi_pct`, `cost_status` | Meta spend for the same window and the resulting return metrics. `cost_status: "no_spend"` when no spend is recorded (ROAS/ROI read `null`, not a misleading infinity). |

The report-level totals (`total_entry_count`, `total_unique_contacts`, `total_attributed_revenue_cents`, spend, and overall ROAS/ROI) sit alongside a per-ad `ads[]` list sorted best-earners first, then busiest by entry volume.

`window_days` (1–365, default 30) bounds the lookback. The same `window_days` also drives the Meta spend fetch — so a tighter window answers "did last week's ads drive this" and a longer window captures nurture, with numerator and denominator always aligned to the same period.

## 3. Proven against a fixture

The funnel's correctness is pinned by a paired regression test that runs the route against a fixture of referral and order rows, asserting the first-touch attribution, the currency-unconvertible surfacing, and the ROAS math. That's what lets you trust that a renamed or dropped column becomes a caught error at the database boundary rather than silently empty attribution — the read validates each row against a schema before aggregating. You don't need to run anything; this is why the funnel reconciles to your ledger instead of drifting.

## 4. How to read it in the dashboard

Call it directly:

```bash theme={null}
curl "https://api.orbit.devotel.io/api/v1/ads/ctwa/attribution?window_days=30" \
  -H "X-API-Key: $ORBIT_API_KEY"
```

* **No connected Meta ad account?** You still get entry volume, customers, and revenue — ROAS reads unavailable (`spend_available: false`) rather than a fabricated return. Connect under Settings → Channels to light spend up.
* **ROAS is revenue/spend, not margin.** A `4.2` ROAS is $4.20 back per $1 of ad spend; `roi_pct` is the same ratio as a percentage of spend.
* **Multi-currency** folds foreign-order revenue into the reporting currency via the shared FX source; anything unconvertible is shown separately. `reporting_currency` echoes what every figure is denominated in.

## 5. Reading vs setup

The [ads activation guide](/guides/ads-activation) walks the full loop — connect Meta, activate a CDP segment, launch the click-to-chat campaign, and post conversions back. This page is the interpretation side once that setup exists. The [QR guide](/guides/qr-codes) covers ad-free click-to-chat entry (print/web QR codes) — those scans don't carry a CTWA referral stamp, so they won't appear in this ad-attribution funnel. Use the right guide for the side you're on.

## 6. Troubleshooting

* **No entry points in the funnel.** The referral stamp lives on inbound click-to-chat messages only. If CTWA customers are chatting but the funnel is empty, the inbound webhook isn't stamping the referral — check your WhatsApp channel webhook configuration, since that's where the stamp is recorded.
* **`403` on the call.** Reads are open to any authenticated role; if you see 403, the API key lacks an authenticated role — issue a key with the right role.
* **`CHANNEL_UNAVAILABLE`.** Connect a Meta ad account first (`POST /ads/connect`), then re-read `/ads/status`.
* **ROAS null.** `cost_status: "no_spend"` means no spend was returned for the window — either the ad account isn't connected or Meta returned no ad-level insights for those ad ids in that period.

## See also

* [Ads API reference](/api-reference/ads) — endpoint schemas for this surface
* [Ads activation guide](/guides/ads-activation) — the setup side (connect, launch, forward conversions)
* [Click-to-chat QR codes](/guides/qr-codes) — ad-free click-to-chat entry
* [Campaign ROAS and revenue attribution](/guides/campaign-roas-attribution) — the messaging/email sibling funnel
* [Referrals](/api-reference/referrals) and [analytics](/api-reference/analytics) — cross-check the funnel against the event log
