Skip to main content

Troubleshooting: Ads hub failures

The Ads hub has five failure states that look broken but are each a specific, expected gate: every endpoint answers CHANNEL_UNAVAILABLE until a Meta Ad Account is connected, the test conversion panel only records sends Meta confirmed, a conversion post without a match key is refused, an audience sync caps at 100,000 members, and the Lead Ads paste box rejects text that is not JSON. Each has a fixed cause and a fixed fix. Work the section that matches your symptom; the field-level vocabulary for every surface on the hub is in the Ads hub reference.

Everything returns CHANNEL_UNAVAILABLE

Until a connection is stored, every Ads endpoint except GET /ads/status returns the same envelope:
On the hub itself this state renders as the Connect Meta Ad Account card on all four tabs. That card is the not-connected state. A failed status read is a different state and renders differently: If CHANNEL_UNAVAILABLE persists after a connect that GET /ads/status confirms with connected: true, you are calling a different organization than the one you connected (API key or session belongs to another workspace).

The conversion test event never turns green

Send Test Event on the Conversions tab posts a test event to the Meta Conversions API. The Recent Test Events panel next to it is session-scoped and only ever logs a send Meta confirmed with events_received > 0. A rejected send therefore never turns green and never appears in the panel. That is the panel working as designed, not a rendering bug: A confirmed send renders the received count (for example “1 received”) in the panel. If repeated sends across several minutes all return 200 with nothing logged, the dataset/ad-account pairing at Meta is the thing to inspect; the flash of the connection is not.

422 on Post Conversion

POST /ads/conversions requires at least one match key in user_data: ctwa_clid (the deterministic Click-to-WhatsApp click id), or phone, email, external_id (Orbit SHA-256 hashes these before they leave the platform; send them raw). A body with none of the four is refused with a deterministic VALIDATION_ERROR envelope (HTTP 400 on this endpoint; the other Ads writes use 422) whose details name the missing match key. Never retry it verbatim; add a key and send once. Match-key menu: Worked body with the deterministic key:
Worked body with a hashed identifier (send the raw value; the hashing happens before the event leaves Orbit):
A refusal you see here is payload-shaped, not connection-shaped: if the envelope instead says CHANNEL_UNAVAILABLE, the account or dataset is the problem and the previous two sections apply.

Audience sync reports truncated: true

POST /ads/audiences/:audienceId/sync resolves the linked segment’s members and uploads them hashed, capped at 100,000 members per sync. Past the cap the response carries truncated: true:
matched is the members resolved from the segment, sent the members handed to Meta. Sync metadata (last_synced_at, last_sync_count) on the audience row updates after each successful sync.

Lead Ads paste rejected / import tallies

The Lead Ads tab accepts the raw leadgen webhook batch Meta delivered to your endpoint. The paste box checks one thing client-side: the text must parse as JSON. Anything else is rejected in the browser with “That doesn’t look like valid JSON. Paste the Meta leadgen webhook payload.” and nothing is sent. Valid JSON that carries no leadgen changes is sent and answers received: 0. After a real import, the Last import panel shows the tally: received always equals fetched + fetch_failures + skipped_no_contact; the created/updated/duplicates split applies to the fetched drafts. Every imported lead goes through the same dedupe, consent, and audit path as a CSV import, so a lead that appears under updated or duplicates was not lost — it matched a contact you already have.

See also