Ads console walkthrough: connect, campaign, CAPI, attribution
This page walks the Ads console operation by operation: connect a Meta Ad Account over OAuth, create click-to-chat campaigns into PAUSED, verify Conversions API (CAPI) delivery with a test event, post real conversions, ingest Lead Ads form submissions, and read attribution over the API. The endpoint schemas live in the Ads API reference; the attribution model is ads attribution and lead ingestion (concepts); the deliberate scope boundary is Ads: feature boundaries. If you want the pre-computed linear version of the same loop (one lead, one conversion, one ROAS read), see your first attributed lead. One correction to how you point the page: Attribution is endpoint-only — the Ads console has four tabs (Ad Campaigns, Conversions, Audiences, Lead Ads). The console deliberately does not re-derive what the API computes; point your own dashboard at the endpoint and render what it returns.Where it lives
Open Outbound > Ads (Ads is a tile on the Outbound hub). The page splits into four tabs, and the campaigns tab doubles as the connect surface:
There is no Attribution tab — the readout is endpoint-only. The tiles map with the question each tile answers is Ads: feature boundaries.
Object-verb map
Everything the Ads surface does, one line per operation:
Role map: every authenticated role reads status, campaigns, audiences, and attribution. Writes — connect/disconnect, campaign create/pause/resume/delete, audience create/sync/delete, conversions configuration/test/post, lead ingestion — need owner or admin.
Connect a Meta ad account over OAuth
One ad account connects per organization; the connection records its billing currency so budgets and spend render in that currency. Meta Ads is early-access — you can connect and launch today.1
Open the connector card
In Settings > Channels, find the Meta Ads card and choose Connect via Settings. The Ads page links there directly.
2
Authorize with Meta
Sign in with your Facebook profile and pick the ad account in Meta’s account picker. Orbit exchanges the OAuth code for a token, upgrades it to a long-lived token, takes the first ad account, and encrypts it at rest.
3
Confirm the linked account
Back on the Ads page, the connect banner turns green and names the connected account id. Every other workspace member with a role can then read campaigns and audiences; only owner/admin can write.
Disconnect
The Disconnect button on the green banner opens a confirmation dialog that asks you to typeDISCONNECT — all-caps, case-sensitive — before the button enables. The dialog states plainly: “This stops live data flow from your Meta Ad Account and disables click-to-chat campaigns. You’ll need to re-authorize with Meta to reconnect.” The console gates disconnect behind a typed phrase on purpose: it deletes the stored connection and requires a fresh OAuth authorization to redo.
Create a campaign — PAUSED at creation
On Ad Campaigns > Create campaign, fill:- Campaign Name — required; placeholder suggests something like “Summer WhatsApp Leads”.
- Daily Budget — required, number ≥ 1 with 0.01-step increments, in the ad account’s billing currency (the field label carries the account currency code, e.g.
(EUR)). - Destination —
WHATSAPP(WhatsApp),MESSENGER(Messenger),INSTAGRAM_DIRECT(Instagram Direct). These are the only three destination values the API accepts. - Ad Text — required (the ad copy Meta serves).
- Image URL — optional; must be a blank-or-safe-HTTPS URL server-side.
- Welcome Message — optional; the greeting text the click-to-chat entry shows.
OUTCOME_LEADS default, or OUTCOME_TRAFFIC / OUTCOME_ENGAGEMENT) and targeting (countries[], age bounds 18–65) over the API; the console form keeps the four fields above required.
New campaigns are always created with status: "PAUSED" — a server-side default you cannot override at create time (the console never sends a status). That is deliberate: review the campaign in Meta before spending, then flip it active from the campaign row menu with Resume. Because it starts PAUSED, Meta-side ad review has already run by the time delivery begins.
Row menu actions per status: Pause, Resume, View Insights (per-campaign funnel — impressions, reach, clicks, CTR, spend, cost per click, conversations rendered at the currency’s precision), and Delete. Delete is permanent at Meta as well as here.
Audiences: create, sync, delete
The Audiences tab activates a CDP segment as a hashed Meta Custom Audience — for targeting, or as a suppression list. Email and phone are SHA-256 hashed before they leave Devotel; the same hashing layer both directions (audience sync + conversion match keys).- Create audience — name, optional description, optional linked CDP segment (search segments by name; choose No segment (link later) to create empty). Server-side you can create with or without a
segment_id. - Sync members — a dialog with two modes: Add members (targeting) and Remove members (suppression). Choose Sync now. The sync pages contacts 10,000 per Meta call and caps at 100,000 members; past the cap it returns
truncated: trueso you know the membership did not fully ship. - Delete — removes the Custom Audience from Meta and removes the segment link. The confirmation warns this cannot be undone.
CAPI: test-then-post loop
The conversion path has three cards, in this order:- Configuration — Dataset ID. Enter the Meta dataset id (the id lives under the centralized Meta connection settings). Save it here; on the wrong card, Send Test Event rejects with
CAPI not configured(the console surfaces the failure message, and the API returnsConversions API not configured. Connect your Meta Ad Account and set a dataset ID.). - Test event. The Send Test Event button posts a sample
Leadevent. The Recent Test Events panel is a session-scoped accept log — the ten most recent sends that Meta confirmed with{count} received. It only logs a row when Meta acknowledgedevents_received > 0; a rejected send never becomes a false green. Each accepted entry also shows Meta’s fbtrace id when the response carried one. Fire the test path before live traffic on every campaign. - Post conversion. The form asks for an event name —
Lead,Purchase,CompleteRegistration,Schedule,Contact,CustomizeProduct,ViewContent, or any Meta standard event up to 64 characters — plus at least one match key: actwa_clidclick id or a phone number (email and an external id are also accepted by the API). Optionally addvalue(a non-negative number) andcurrency(a 3-letter ISO code; the form uppercases it before posting). The button stays disabled until a match key is present, because without one Meta cannot attribute the event. Phone, email, and external id are SHA-256 hashed server-side before they leave; thectwa_clidtravels as-is (it is Meta’s own click id, deterministic).
The deterministic join vs. the probabilistic one
When a conversion posts with thectwa_clid from the inbound click-to-chat message, Meta joins it to the entry ad deterministically. When it posts with only a hashed phone/email/external id, Meta matches it to an ad impression probabilistically — iOS privacy and tracking-protection knockouts reduce this rate on Meta’s side. The console prompts you toward the ctwa_clid whenever you have one and the attribution model records a much stronger match. Lost match expectation: some phone/email-only conversions land unattributed on Meta’s side — the click id survives, the hashed identity does not always.
Dedup between pixel and server events
If you also run a browser pixel elsewhere, keep the event id stable across both paths: Meta dedups same-name same-id events server-side vs pixel-side. Orbit only ever sends server-side, so pixel-vs-server duplication never originates from this surface.Route webinars: Lead Ads ingestion
The Lead Ads tab ingests Meta lead-ad form submissions by pasting theleadgen webhook payload your webhook endpoint received. Paste the exact envelope:
leadgen changes are extracted; each leadgen_id is fetched from Meta’s Graph (fields: field_data, created_time, ad_id, ad_name, adset_id, campaign_id, campaign_name, form_id), then mapped onto contacts with email and/or phone and upserted through the same dedupe-consent-audit path as a CSV import. A lead with neither email nor phone is skipped (skipped_no_contact). Import leads returns a tally: Received, Fetched, Created, Updated, Duplicates, Skipped (no contact info), and Fetch failures — per-lead failures never abort the batch.
Lead Ads is a second entry path only: it lands in the same attribution view as click-to-chat entries. ctwa_clid matching does not apply to lead-form entries (there is no ctwa_clid to reference), so those leads attribute through the hashed identity match.
Attribution: the API-only tab
Attribution is the roll-up the API endpointGET /ads/ctwa/attribution computes. There is no Attribution tab in the Ads console — point your own dashboard at the endpoint and render what it returns. The one query parameter is window_days (integer, 1–365, default 30): entry points older than the window do not count, and Meta ad spend is fetched over the same window so the ROAS numerator and denominator align.
$window is 1, 7, or 30 for the yesterday/week/month presets (any integer 1–365 works):
reporting_currency (your ad account’s billing currency), window_days, total_entry_count, total_unique_contacts, total_attributed_revenue_cents, total_unconvertible_revenue_cents, total_spend_cents, roas, roi_pct, cost_status, spend_available, and ads[] — one row per entry ad with ad_id, first/last seen, entry_count, unique_contacts, attributed_revenue_cents, unconvertible_revenue_cents, spend_cents, roas, roi_pct, cost_status. Attribution is first-touch: the earliest captured referral fixes the entry ad; per-ad contact counts then stay partition-clean (summing across ads never double-counts). Revenue in a currency with no caller-supplied FX rate surfaces in unconvertible_revenue_cents rather than blending at 1:1. cost_status is exactly ok or no_spend — when no account is connected or spend fetch failed, the report still returns entry attribution with roas: null, spend_available: false, cost_status: "no_spend".
The full loop in one read
Closed loop, end to end — the six operator steps from ad dollar to attributed ROAS:- Connect — authorize one Meta Ad Account over OAuth (Settings > Channels > Meta Ads); the connect banner goes green and names the account id.
- Create the campaign — Ad Campaigns > Create campaign with a WhatsApp, Messenger, or Instagram Direct destination. It lands
PAUSED; review it in Meta Ads Manager, then flip it live with the row-menu Resume. - A click lands as a conversation — a customer taps the ad; the entry message arrives in your Inbox already carrying the Meta referral stamp (the
ctwa_clidand the entry ad id), so the entry is attributed the moment it arrives. - The outcome posts back — when the conversation converts, post the business outcome (
Lead,Purchase, …) through Conversions > Post Conversion with thatctwa_clidas the match key — a deterministic join back to the entry ad. Meta bids toward more of those clicks. - Attribution closes the loop — read
GET /ads/ctwa/attribution?window_days=(first-touch; the earliest entry fixes the ad a contact belongs to). Per-ad entry counts, attributed revenue, and Meta spend over the same window divide out to ROAS; a campaign that earns more than it spent showsroas > 1and positiveroi_pcton its ad rows.
Troubleshooting
- Connect banner shows an error card instead of an account. The connection-status read failed (401/403/500 from
/ads/status); the console surfaces “Couldn’t load Meta Ad Account status. Try again.” with a retry control. Fix the read before re-running OAuth — re-authorizing against a failing read recreates the same 500. - OAuth token expiry / revoked access. The banner still names the account, but campaign syncs and conversion posts start failing because the stored token can no longer be used (expired, the Meta user de-authorized the app, or the account’s access changed). In the Meta app yourself, a revoked grant also shows under your Facebook profile’s Business Integrations. The fix is the same as a fresh connect: re-run the OAuth flow from Settings > Channels > Meta Ads — the stored token upgrades to a new long-lived one. Campaigns, audiences, and the dataset survive a re-connect untouched; only the credentials change.
- Disconnect button greyed out despite typing. The required phrase is
DISCONNECT— all-caps, case-sensitive. Typingdisconnectdoes not enable the button. - Send Test Event rejected with
CAPI not configured. The dataset id is unset or saved on the wrong card. Save it under Conversions > Configuration exactly, then resend. - Meta returns HTTP 200 with zero received events. A silent reject — the dataset is paused or misconfigured at the Meta side. The accept gate catches it: the toast flags the failure and Recent Test Events does not log it. Note the API boundary conversationally: CAPI errors from Meta surface to you as a generic 500 with only the serving-side digest; the raw Graph error string is logged server-side and to Sentry, which is why the test-then-post loop is the ground truth, not the HTTP status.
- Post Conversion stays disabled. Provide at least one match key (
ctwa_clidor a phone) so Meta can attribute the event. ctwa_clidmatch failures. Capture the click id from the inbound message at entry time — the console cannot reconstruct it later. An event posted with only a hashed phone/email tells Meta the outcome happened but gives it no entry ad to credit. First-touch attribution also keeps a contact on the earliest ad; a later click on a different ad does not re-attribute the contact.- Lead-ad entries show no
ctwa_clid. Lead Ads instant-form submissions have no click id to reference — a form entry never carried one — so those leads attribute through the hashed email/phone identity match, the probabilistic join. This is expected, not a broken import: expect a weaker Meta match rate on form entries than on click-to-chat entries, and prefer the click-to-chat entry path where attribution certainty matters. - Value entered but rejected.
valuemust be a non-negative number;currency, when set, must be a three-letter ISO code — the form uppercases before posting. - Audience sync reports truncated. Membership exceeded the 100,000-member cap. Narrow the segment and re-sync.
- Lead Ads import skips leads. A lead with no email and no phone is skipped — check the field mapping in your form builder at Meta before re-importing.
Privacy guardrails: hashed PII and what leaves
Match keys you post — phone, email, external id — are SHA-256 hashed server-side before they leave Devotel; thectwa_clid travels as-is because it is Meta’s own click id. Audience sync applies the same hashing layer to phone/email membership. Attribution reads only the referral blocks on inbound messages — no raw PII crosses either direction. These are tenant-owned controls: Orbit is the conduit, and consent to use a contact’s data in ad platforms sits on your side of the consent line — keep your audience source segments aligned with the audience consent inspector and opt-out lists.
Scope and boundary guardrails — what NOT to build here
The Ads console is deliberately scoped to Meta click-to-chat only:- Non-Meta networks. No Google Ads, no TikTok Ads, no X/LinkedIn/Snapchat — none of them expose the deterministic
ctwa_clidjoin, and folding a weaker attribution in would be a downgrade. Any new network ships as its own connector with its own documentation, announced on the changelog. - Creative / media-buying work. The console manages the campaign loop (create, pause/resume, delete, audiences, conversions, attribution) — not creative production, ad-set-level trade-craft, bidding strategy, or account-level Meta Ads Manager work. Those stay on Meta’s side.
- Lead Ads is form ingestion only. A lead-form submission lands in the same attribution view as a click-to-chat entry — it is ingestion of a Meta-native form, not a general lead-capture builder for other networks.
See also
- Ads API reference — every endpoint behind this console
- Meta Ads activation guide — the full segment-to-attribution loop
- Ads attribution and lead ingestion (concepts) — the attribution model this console implements
- CDP audiences: build segments — the segment recipe for Custom Audiences
- Opt-out lists — tenant-owned consent and suppression controls for ad audiences
- Audience consent inspector — verify consent before syncing segments into ad audiences