Click-to-WhatsApp Ads
The ctwa command (alias: meta-ads) mirrors /settings/ctwa. It
wraps three sub-surfaces:
- OAuth code exchange — finishing the Meta “Connect” flow on the backend after the browser-side OAuth dance returns a code.
- Conversion API (CAPI) — the dataset + event trigger config that pushes lead and purchase events back to Meta for ad attribution.
- Ads listing — the per-account ads inventory + per-ad detail (read-only).
Backed by /api/v1/app/meta-ads/*.
Quick start
# 1. Status — has CAPI been wired up yet?
splashify ctwa # default = capi status
# 2. Connect Meta (browser does the OAuth dance, paste the code here)
splashify ctwa exchange-code --code AQB0Lg…JaYHFq8KQ
# 3. Configure CAPI events
splashify ctwa capi save \
--lead-enabled true \
--lead-trigger "form_submitted" \
--lead-tag "lead" \
--purchase-enabled true \
--purchase-trigger "order_placed" \
--purchase-tag "buyer" \
--purchase-currency INR
# 4. Test-fire a lead event for one contact
splashify ctwa capi send-event --event-type lead --phone +919876543210
# 5. See ads under your connected account
splashify ctwa ads
splashify ctwa ad <ad_id>How the CTWA wiring works
┌─────────────┐ 1. OAuth dance ┌─────────────────┐
│ You │ ───────────────────────► │ Meta business │
│ (browser) │ (in business.facebook │ (graphical) │
└──────┬──────┘ .com) └────────┬────────┘
│ │
│ 2. redirect with ?code=… │
▼ │
┌─────────────┐ 3. ctwa exchange-code ▼
│ splashify │ ───────────────────────► ┌─────────────────┐
│ CLI │ │ partnersapi │
└──────┬──────┘ ◄─────────────────────── │ /meta-ads/* │
│ 4. tokens stored └────────┬────────┘
│ │
▼ │
┌─────────────┐ ▼
│ ctwa capi │ ┌─────────────────┐
│ save / send│ ───────────────────────► │ Meta Graph API │
└─────────────┘ conversion events └─────────────────┘The CLI handles steps 3, 4, and 5. Step 1 still requires a real browser (Meta requires you to authenticate on business.facebook.com).
Command tree
ctwa show CAPI status (default)
ctwa capi show CAPI config
ctwa capi save update CAPI config
ctwa capi send-event manually fire a conversion event (test)
ctwa exchange-code --code <code> finish the OAuth handshake
ctwa refresh-token refresh the stored Meta token
ctwa ads list ads on the connected Meta account
ctwa ad <ad_id> show one adOAuth code exchange
After clicking Connect Meta in the browser, Meta redirects to your
callback URL with ?code=…. Copy that code and run:
splashify ctwa exchange-code --code AQB0Lg…JaYHFq8KQ
splashify ctwa exchange-code --code AQB0… \
--granted-scopes '["ads_management","pages_show_list"]' \
--redirect-uri "https://app.splashifypro.com/settings/ctwa/callback"| Backed by | POST /api/v1/app/meta-ads/exchange-code |
|---|
| Flag | Required | Notes |
|---|---|---|
--code | yes | Authorization code from the redirect |
--granted-scopes | no | JSON array of granted scope names (Meta provides this in the redirect) |
--redirect-uri | no | Must match the URI registered on the Meta app |
The backend exchanges the code for a long-lived token, encrypts it, and stores it. CAPI commands work after this succeeds.
Refresh the stored token
splashify ctwa refresh-token| Backed by | POST /api/v1/app/meta-ads/refresh-token/:user_id |
|---|
The CLI resolves :user_id from /app/me automatically — you don’t pass
it. Useful when CAPI calls start failing with token-expiry errors.
CAPI — Conversion API
CAPI sends conversion events to Meta for ad attribution. Two event types are supported:
| Event type | Fired on | Required fields |
|---|---|---|
| lead | A new lead converts | lead_event_trigger matches |
| purchase | A purchase completes | purchase_event_trigger matches, plus optional value + currency |
View the config
splashify ctwa capi # default: GET the config
splashify ctwa capi status| Backed by | GET /api/v1/app/meta-ads/capi |
|---|
Response shape:
{
"success": true,
"capi": {
"dataset_id": "<meta_dataset_id>",
"lead_event_enabled": true,
"lead_event_trigger": "form_submitted",
"lead_event_tag": "lead",
"purchase_event_enabled": true,
"purchase_event_trigger": "order_placed",
"purchase_event_tag": "buyer",
"purchase_event_currency": "INR",
"purchase_event_value": 0,
"connected": true
}
}Save the config
# First-time save — leave --dataset-id empty so the backend auto-creates one on Meta
splashify ctwa capi save \
--lead-enabled true \
--lead-trigger "form_submitted" \
--lead-tag "lead" \
--purchase-enabled true \
--purchase-trigger "order_placed" \
--purchase-tag "buyer" \
--purchase-currency INR \
--purchase-value 0| Backed by | POST /api/v1/app/meta-ads/capi |
|---|
| Flag | Notes |
|---|---|
--dataset-id | Existing Meta dataset_id. Omit to auto-create. |
--lead-enabled | true | false |
--lead-trigger | Event name in your workflow that fires the Lead conversion (free-form — e.g. form_submitted) |
--lead-tag | Contact tag to apply when the lead event matches |
--purchase-enabled | true | false |
--purchase-trigger | Event name (e.g. order_placed) |
--purchase-tag | Contact tag to apply on purchase |
--purchase-currency | ISO 4217 (e.g. INR, USD) |
--purchase-value | Default monetary value when not in payload |
Only the fields you pass are sent — the rest stay unchanged.
Send a test event
# Lead
splashify ctwa capi send-event --event-type lead --phone +919876543210
# Purchase with value
splashify ctwa capi send-event \
--event-type purchase \
--phone +919876543210 \
--value 1499 \
--currency INR| Backed by | POST /api/v1/app/meta-ads/capi/send-event |
|---|
| Flag | Required | Notes |
|---|---|---|
--event-type | yes | lead | purchase |
--phone | yes | E.164 phone of the target contact |
--value | for purchase | Monetary value |
--currency | for purchase | ISO 4217 code |
Useful for verifying your dataset_id + triggers are working before
waiting on a real signal.
Ads inventory
splashify ctwa ads # list ads on the connected account
splashify ctwa ad <ad_id> # one ad in detail| Backed by | GET /api/v1/app/meta-ads/:user_id |
|---|---|
GET /api/v1/app/meta-ads/:user_id/ad/:ad_id |
:user_id is auto-resolved from /app/me.
Common workflows
One-time CAPI bootstrap
# 1. Verify Meta is connected
splashify ctwa | jq '.capi.connected'
# 2. Save the config (let the backend auto-create the dataset)
splashify ctwa capi save \
--lead-enabled true --lead-trigger "form_submitted" --lead-tag "lead" \
--purchase-enabled true --purchase-trigger "order_placed" \
--purchase-tag "buyer" --purchase-currency INR
# 3. Test
splashify ctwa capi send-event --event-type lead --phone +919876543210
# 4. Then watch /events in Meta Events Manager appearFire purchase events from a script
# After an order placed in your e-commerce backend
splashify ctwa capi send-event \
--event-type purchase \
--phone "$CUSTOMER_PHONE" \
--value "$ORDER_TOTAL" \
--currency INRAudit which ads are running on your account
splashify ctwa ads | jq '.ads[] | {ad_id, name, status, daily_budget}'Pause CAPI without disconnecting Meta
splashify ctwa capi save --lead-enabled false --purchase-enabled falseTroubleshooting
exchange-code returns 400 “Invalid code” — codes are single-use
and expire within minutes. Restart the OAuth dance from the browser to
get a fresh code.
exchange-code returns 400 “Required scopes missing” — the user
declined a scope during the OAuth flow. Re-run the browser dance and
accept all requested scopes (ads_management, pages_show_list, and
the CAPI-related scopes).
capi save returns 503 “Meta not connected” — run
splashify ctwa exchange-code --code <code> first.
capi save auto-create dataset returns 500 — Meta’s Graph API
returned an error. Verify the Meta business has CAPI enabled
(business.facebook.com → Events Manager → Data Sources). Pre-create
the dataset there, then pass --dataset-id <id> to skip auto-create.
capi send-event returns 200 but Meta Events Manager doesn’t show
it — Meta deduplicates events; the same phone + event combination
within ~60s only counts once. Wait or use a different phone for
testing.
refresh-token returns 401 — the stored token is unrecoverable
(usually the user revoked the app on facebook.com → Apps and websites).
Re-run exchange-code with a fresh code.
See also
splashify subscription— confirms CAPI/CTWA is on your plan.splashify contacts— set the--lead-tag/--purchase-tagvalues on contacts manually for testing.splashify expenses— per-message billing for the CTWA-triggered conversions (events are free, but the WhatsApp template they trigger is metered).