Skip to Content
splashify CLIClick-to-WhatsApp Ads

Click-to-WhatsApp Ads

The ctwa command (alias: meta-ads) mirrors /settings/ctwa. It wraps three sub-surfaces:

  1. OAuth code exchange — finishing the Meta “Connect” flow on the backend after the browser-side OAuth dance returns a code.
  2. Conversion API (CAPI) — the dataset + event trigger config that pushes lead and purchase events back to Meta for ad attribution.
  3. 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 ad

OAuth 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 byPOST /api/v1/app/meta-ads/exchange-code
FlagRequiredNotes
--codeyesAuthorization code from the redirect
--granted-scopesnoJSON array of granted scope names (Meta provides this in the redirect)
--redirect-urinoMust 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 byPOST /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 typeFired onRequired fields
leadA new lead convertslead_event_trigger matches
purchaseA purchase completespurchase_event_trigger matches, plus optional value + currency

View the config

splashify ctwa capi # default: GET the config splashify ctwa capi status
Backed byGET /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 byPOST /api/v1/app/meta-ads/capi
FlagNotes
--dataset-idExisting Meta dataset_id. Omit to auto-create.
--lead-enabledtrue | false
--lead-triggerEvent name in your workflow that fires the Lead conversion (free-form — e.g. form_submitted)
--lead-tagContact tag to apply when the lead event matches
--purchase-enabledtrue | false
--purchase-triggerEvent name (e.g. order_placed)
--purchase-tagContact tag to apply on purchase
--purchase-currencyISO 4217 (e.g. INR, USD)
--purchase-valueDefault 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 byPOST /api/v1/app/meta-ads/capi/send-event
FlagRequiredNotes
--event-typeyeslead | purchase
--phoneyesE.164 phone of the target contact
--valuefor purchaseMonetary value
--currencyfor purchaseISO 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 byGET /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 appear

Fire 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 INR

Audit 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 false

Troubleshooting

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-tag values 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).