# CAPI Conversion Reporting Endpoint

Send Connectly your post-WhatsApp conversion events; we forward them to Meta's Conversions API on your behalf so the originating CTWA ad gets credit. You don't talk to Meta directly.

### Endpoint

```
POST https://api.connectly.ai/external/v1/businesses/{business_id}/conversion_events
Content-Type: application/json
X-API-KEY: <your_plaintext_key>
```

\
`{business_id}` — your Connectly business UUID (Connectly will provide).

### Example request

```
curl -i -X POST "https://api.connectly.ai/external/v1/businesses/<BUSINESS_ID>/conversion_events" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <YOUR_API_KEY>" \
  -d '{
    "cnct_tracking_id": "<value from URL>",
    "event_name":       "Purchase",
    "sendout_id":       "<value from URL>",
    "attribution": {
      "meta_ctwa": {
        "ctwa_clid": "<value from URL>",
        "ad_id":     "<value from URL>"
      }
    },
    "payload": {
      "currency":  "USD",
      "value":     99.97,
      "order_id":  "ORD-7821",
      "content_type": "product",
      "content_ids":  ["data-pass-30d", "phone-case-blk"],
      "num_items":    2,
      "contents":  [
        { "id": "data-pass-30d",  "quantity": 1, "item_price": 49.99 },
        { "id": "phone-case-blk", "quantity": 1, "item_price": 49.98 }
      ],
      "event_time": 1746480000
    }
  }'
```

\
All four query-param values (`cnct_tracking_id`, `sendout_id`, `ctwa_clid`, `ad_id`) are auto-injected by Connectly on the carousel CTA URL when the campaign has external purchase tracking enabled. Capture them at landing and persist them however you prefer so they're still available at conversion time.

### Top-level fields

| <p>Field<br></p>                           | <p>Type<br></p>   | <p>Required<br></p> | <p>Description<br></p>                                                                                                                                                                 |
| ------------------------------------------ | ----------------- | ------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>cnct\_tracking\_id</code><br></p> | <p>string<br></p> | <p>yes<br></p>      | <p>Verbatim copy of the <code>cnct\_tracking\_id</code> query param Connectly puts on the landing URL. Identifies the originating WhatsApp/CTWA session at the customer level.<br></p> |
| <p><code>event\_name</code><br></p>        | <p>string<br></p> | <p>yes<br></p>      | <p>Meta CAPI event name. Currently <code>"Purchase"</code> and <code>"ViewContent"</code> are accepted; anything else returns 400 INVALID\_ARGUMENT.<br></p>                           |
| <p><code>sendout\_id</code><br></p>        | <p>string<br></p> | <p>yes<br></p>      | <p>Verbatim copy of the <code>sendout\_id</code> query param Connectly puts on the same landing URL. Lets us credit the conversion to the correct campaign.<br></p>                    |
| <p><code>attribution</code><br></p>        | <p>object<br></p> | <p>yes<br></p>      | <p>The CTWA attribution payload. Single nested key: <code>meta\_ctwa</code>. See below.<br></p>                                                                                        |
| <p><code>payload</code><br></p>            | <p>object<br></p> | <p>yes<br></p>      | <p>Per-event detail. Required for <code>Purchase</code> (must contain at least <code>currency</code> + <code>value</code>).<br></p>                                                    |

<br>

#### `attribution.meta_ctwa` fields

| <p>Field<br></p>                   | <p>Type<br></p>   | <p>Required<br></p> | <p>Description<br></p>                                                                                                                                                                                                                                                                                                  |
| ---------------------------------- | ----------------- | ------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>ctwa\_clid</code><br></p> | <p>string<br></p> | <p>yes<br></p>      | <p>Copy verbatim from the <code>ctwa\_clid</code> query param Connectly puts on the landing URL (same value Meta originally attached as <code>referral.ctwa\_clid</code> on the WhatsApp inbound). This is the value forwarded to Meta CAPI as <code>user\_data.ctwa\_clid</code> to credit the originating ad.<br></p> |
| <p><code>ad\_id</code><br></p>     | <p>string<br></p> | <p>yes<br></p>      | <p>Copy verbatim from the <code>ad\_id</code> query param. Lands on Connectly conversion analytics for per-ad reporting; not forwarded to Meta CAPI itself.<br></p>                                                                                                                                                     |

<br>

### `payload` fields

#### `Purchase` (Meta CAPI strict — `currency` + `value` required)

| <p>Field<br></p>                      | <p>Type<br></p>                   | <p>Required<br></p>    | <p>Description<br></p>                                                                                                                                                |
| ------------------------------------- | --------------------------------- | ---------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>currency</code><br></p>      | <p>string<br></p>                 | <p>yes<br></p>         | <p>ISO 4217 currency code, e.g. <code>"USD"</code>, <code>"BRL"</code>, <code>"MXN"</code>.<br></p>                                                                   |
| <p><code>value</code><br></p>         | <p>number<br></p>                 | <p>yes<br></p>         | <p>Total order value (decimal allowed). E.g. 99.97. Must be ≥ 0.<br></p>                                                                                              |
| <p><code>order\_id</code><br></p>     | <p>string<br></p>                 | <p>recommended<br></p> | <p>Merchant's order reference. Used as Meta's <code>event\_id</code> dedup key against the browser pixel — send the same value the pixel sees. Safe to retry.<br></p> |
| <p><code>content\_type</code><br></p> | <p>string<br></p>                 | <p>optional<br></p>    | <p><code>"product"</code> for SKU-level matching, or <code>"product\_group"</code>.<br></p>                                                                           |
| <p><code>content\_ids</code><br></p>  | <p>string\[]<br></p>              | <p>optional<br></p>    | <p>Array of SKU / product IDs in the order. Use the same IDs as your Meta product catalog. ≤ 100 items.<br></p>                                                       |
| <p><code>num\_items</code><br></p>    | <p>integer<br></p>                | <p>optional<br></p>    | <p>Total cart size (sum of <code>contents\[].quantity</code>).<br></p>                                                                                                |
| <p><code>contents</code><br></p>      | <p>object\[]<br></p>              | <p>recommended<br></p> | <p>Per-line-item array. Preferred over <code>content\_ids</code> alone when quantity matters. ≤ 100 items. See sub-fields below.<br></p>                              |
| <p><code>event\_time</code><br></p>   | <p>integer (unix epoch s)<br></p> | <p>optional<br></p>    | <p>When the order actually occurred. Defaults to receive-time on our side if omitted. Meta rejects events older than 7 days.<br></p>                                  |

<br>

#### `payload.contents[]` item

| <p>Field<br></p>                    | <p>Type<br></p>    | <p>Required<br></p>    | <p>Description<br></p>                                                                                                                                                                      |
| ----------------------------------- | ------------------ | ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>id</code><br></p>          | <p>string<br></p>  | <p>yes<br></p>         | <p>SKU / product ID matching your Meta catalog. Items missing an <code>id</code> are skipped silently.<br></p>                                                                              |
| <p><code>quantity</code><br></p>    | <p>integer<br></p> | <p>recommended<br></p> | <p>Units of this item in the order. Drives Meta's cart-size-aware bid optimization — without it, multi-item orders look like single-item orders to the optimizer.<br></p>                   |
| <p><code>item\_price</code><br></p> | <p>number<br></p>  | <p>recommended<br></p> | <p>Per-unit price (decimal allowed). Lets Meta compute revenue per item, which feeds value-based bidding. Without it, Meta sees only the cart total via <code>payload.value</code>.<br></p> |

<br>

### Response

```
{ "events_received": 1 }
```

<br>

| <p>Field<br></p>                         | <p>Type<br></p>    | <p>Meaning<br></p>                                                                |
| ---------------------------------------- | ------------------ | --------------------------------------------------------------------------------- |
| <p><code>events\_received</code><br></p> | <p>integer<br></p> | <p><code>1</code> = Connectly received and recorded the conversion event.<br></p> |

\
We record the conversion on your campaign analytics regardless of whether the Meta CAPI forward fires. If Meta itself rejects the event, you get a non-200 with `INVALID_ARGUMENT` carrying Meta's verbatim error in the `meta_error` detail (e.g. `"Invalid Ctwa Clid"`, `"Mismatching WhatsApp Business Account ID"`).\ <br>

### Errors

| <p>HTTP<br></p>                               | <p>When<br></p>                                                                                                                                                                                                                                                                                                                                      |
| --------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| <p><code>400 INVALID\_ARGUMENT</code><br></p> | <p>Missing/long <code>cnct\_tracking\_id</code>, <code>event\_name</code>, <code>sendout\_id</code>, or <code>attribution.meta\_ctwa.ctwa\_clid</code> / <code>attribution.meta\_ctwa.ad\_id</code>. Missing <code>currency</code>/<code>value</code> for Purchase. Unsupported <code>event\_name</code>. Meta rejected the forwarded event.<br></p> |
| <p><code>401 Unauthenticated</code><br></p>   | <p>Missing or invalid <code>X-API-KEY</code>.<br></p>                                                                                                                                                                                                                                                                                                |
| <p><code>404 NOT\_FOUND</code><br></p>        | <p>Business not found, or has no WhatsApp Cloud channel configured.<br></p>                                                                                                                                                                                                                                                                          |

<br>

### Authentication

All requests must include `X-API-KEY: <your_plaintext_key>`.\
\
Getting your API key\ <br>

1. Open the Connectly inbox → Settings → General.
2. Scroll to the API Key section.
3. Either:

* Reuse an existing key if you already have one with Full Access (no scope restrictions) and you still have the plaintext saved. If you don't have the original plaintext, you cannot recover it; create a new one.
* Or click *"Create new API key"*:
* Give it a descriptive name (e.g. `capi_integration`).
* Leave all scopes unchecked — the helper text confirms "No scopes selected — key will have full access." This is required: the CAPI webhook does not gate on a specific scope today.
* Pick an expiration (or leave as "No expiration").
* Click *Create Key*.

1. The modal shows the plaintext key exactly once. Copy it immediately and store it somewhere durable. If you lose it, the only recovery path is creating a new key.
2. Send the plaintext to your backend infra so it can be injected into the request to the Connectly endpoint. Never check the plaintext into source control or expose it client-side.

<br>

### Integration journey

1. Get an API key — Connectly inbox → Settings → General → API Key section (see above). Single credential for the integration.

<br>

1. Build your CTWA campaign in flow builder and tick "Track purchases completed on my own site and report them to the Ads Manager" in the Audience step on the Click-to-WhatsApp card. Without this checkbox, your carousel links won't carry the tracking params and we can't credit conversions back.

<br>

1. Send your campaign. From this point, every carousel CTA link Connectly generates is auto-appended with:

`?cnct_tracking_id=<value>&sendout_id=<value>&ctwa_clid=<value>&ad_id=<value>&attribution_source=meta_ctwa`\
\
Persist all four values (`cnct_tracking_id`, `sendout_id`, `ctwa_clid`, `ad_id`) when the customer lands on your site so they're still available at checkout. They're how we map the purchase back to the originating ad and to your campaign analytics.\ <br>

1. On purchase, call us once per order:

```
curl -i -X POST "https://api.connectly.ai/external/v1/businesses/<BUSINESS_ID>/conversion_events" \
  -H "Content-Type: application/json" \
  -H "X-API-KEY: <YOUR_API_KEY>" \
  -d '{
    "cnct_tracking_id": "<value from URL>",
    "event_name":       "Purchase",
    "sendout_id":       "<value from URL>",
    "attribution": {
      "meta_ctwa": {
        "ctwa_clid": "<value from URL>",
        "ad_id":     "<value from URL>"
      }
    },
    "payload": {
      "currency":  "USD",
      "value":     99.97,
      "order_id":  "ORD-7821",
      "content_type": "product",
      "content_ids":  ["data-pass-30d", "phone-case-blk"],
      "num_items":    2,
      "contents":  [
        { "id": "data-pass-30d",  "quantity": 1, "item_price": 49.99 },
        { "id": "phone-case-blk", "quantity": 1, "item_price": 49.98 }
      ],
      "event_time": 1746480000
    }
  }'
```

`order_id` doubles as Meta's dedup key — keep it stable per actual order; safe to retry. We respond `{ events_received:1 }` synchronously.\ <br>

1. We forward to Meta CAPI on your behalf. The conversion shows up in Meta Events Manager → your dataset within a few hours and rolls up into Ads Manager attribution + ROAS. On our side you'll also get analytics: conversions per ad, total revenue, revenue per product id, view-content traffic per ad — all queryable without joining anything against Meta data.

<br>

### What we log on our side

Beyond the HTTP response, every call to `/conversion_events` produces exactly one analytics record on our side — same record on success, validation failure, and Meta rejection. You don't need to do anything to enable this; it happens automatically.\
\
What's captured per call:<br>

* 🟢 Successes: `success: true`, Meta's `events_received` + `fbtrace_id` (recorded internally as `forward_reference_id`), your full payload (`currency`, `value`, `order_id`, `contents`, etc.) + the CTWA attribution (`ctwa_clid`, `ad_id`, `cnct_tracking_id`,`sendout_id`).
* 🔴 Failures: `success: false`, the error message, plus a typed `failure_stage` field telling you exactly where it broke:
* `business_lookup` — your business id is unknown or unavailable.
* `channel_lookup` — your business doesn't have a WhatsApp Cloud channel configured.
* `payload_mapping` — your event payload was rejected by validation (missing currency/value for Purchase, unsupported event\_name, etc.).
* `meta_forward` — Meta itself rejected the event (the `error` field carries Meta's verbatim message, e.g. `"Invalid Ctwa Clid"`, `"Mismatching WhatsApp Business Account ID"`).

\
All of this lands in our analytics warehouse and powers the same dashboards we'll use to tell you, post-launch:\ <br>

* conversions per ad (`ad_id` → `events_received`)
* revenue per ad / per campaign / per product SKU
* failure rates broken down by `failure_stage` — so we can flag "Meta rejected 3% of your events yesterday" before it becomes your problem
* view-content traffic vs purchase conversion rates

If you ever wonder "did Connectly (and Meta) receive my event?" — the answer is in the HTTP response and in our analytics dashboards.\ <br>

### Notes

* One event per call. Multi-item orders go in `contents[]` within a single `Purchase` event — not multiple POSTs.
* `cnct_tracking_id`, `sendout_id`, `ctwa_clid`, `ad_id` need to be persisted by you and passed back to us when calling the conversion endpoint.
* `event_time` should be the actual order timestamp, not when you flush to us — important if you batch overnight or backfill.
* Regardless of what Meta has documented as recommended/optional, for the best experience we recommend populating all the fields shown in the [example request](https://connectly-workspace.slack.com/docs/T01HZ5L13JR/F0B21NC3XJA?focus_section_id=temp:C:CCS3e540d91021e4930bf6675326)


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.connectly.ai/analytics/capi-conversion-reporting-endpoint.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
