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.

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

Field

Type

Required

Description

cnct_tracking_id

string

yes

Verbatim copy of the cnct_tracking_id query param Connectly puts on the landing URL. Identifies the originating WhatsApp/CTWA session at the customer level.

event_name

string

yes

Meta CAPI event name. Currently "Purchase" and "ViewContent" are accepted; anything else returns 400 INVALID_ARGUMENT.

sendout_id

string

yes

Verbatim copy of the sendout_id query param Connectly puts on the same landing URL. Lets us credit the conversion to the correct campaign.

attribution

object

yes

The CTWA attribution payload. Single nested key: meta_ctwa. See below.

payload

object

yes

Per-event detail. Required for Purchase (must contain at least currency + value).

attribution.meta_ctwa fields

Field

Type

Required

Description

ctwa_clid

string

yes

Copy verbatim from the ctwa_clid query param Connectly puts on the landing URL (same value Meta originally attached as referral.ctwa_clid on the WhatsApp inbound). This is the value forwarded to Meta CAPI as user_data.ctwa_clid to credit the originating ad.

ad_id

string

yes

Copy verbatim from the ad_id query param. Lands on Connectly conversion analytics for per-ad reporting; not forwarded to Meta CAPI itself.

payload fields

Purchase (Meta CAPI strict β€” currency + value required)

Field

Type

Required

Description

currency

string

yes

ISO 4217 currency code, e.g. "USD", "BRL", "MXN".

value

number

yes

Total order value (decimal allowed). E.g. 99.97. Must be β‰₯ 0.

order_id

string

recommended

Merchant's order reference. Used as Meta's event_id dedup key against the browser pixel β€” send the same value the pixel sees. Safe to retry.

content_type

string

optional

"product" for SKU-level matching, or "product_group".

content_ids

string[]

optional

Array of SKU / product IDs in the order. Use the same IDs as your Meta product catalog. ≀ 100 items.

num_items

integer

optional

Total cart size (sum of contents[].quantity).

contents

object[]

recommended

Per-line-item array. Preferred over content_ids alone when quantity matters. ≀ 100 items. See sub-fields below.

event_time

integer (unix epoch s)

optional

When the order actually occurred. Defaults to receive-time on our side if omitted. Meta rejects events older than 7 days.

payload.contents[] item

Field

Type

Required

Description

id

string

yes

SKU / product ID matching your Meta catalog. Items missing an id are skipped silently.

quantity

integer

recommended

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.

item_price

number

recommended

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 payload.value.

Response

Field

Type

Meaning

events_received

integer

1 = Connectly received and recorded the conversion event.

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").

Errors

HTTP

When

400 INVALID_ARGUMENT

Missing/long cnct_tracking_id, event_name, sendout_id, or attribution.meta_ctwa.ctwa_clid / attribution.meta_ctwa.ad_id. Missing currency/value for Purchase. Unsupported event_name. Meta rejected the forwarded event.

401 Unauthenticated

Missing or invalid X-API-KEY.

404 NOT_FOUND

Business not found, or has no WhatsApp Cloud channel configured.

Authentication

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

  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.

Integration journey

  1. Get an API key β€” Connectly inbox β†’ Settings β†’ General β†’ API Key section (see above). Single credential for the integration.

  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.

  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.

  1. On purchase, call us once per order:

order_id doubles as Meta's dedup key β€” keep it stable per actual order; safe to retry. We respond { events_received:1 } synchronously.

  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.

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:

  • 🟒 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:

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

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 requestarrow-up-right

Last updated