Skip to content
Splashify Pro
Docs

WhatsApp Calling

Available for accounts in India.

The calling command mirrors the /calling page and call is its per-id sibling. Together they cover everything the page does except the browser-only WebRTC live-audio bridge (intentionally — it's a real-time audio stream that has no place in a CLI).

Backed by /api/v1/app/calling/*. Like broadcasts and email, every send runs a subscription + wallet preflight before submission.

Quick start

bash
splashify calling                                # overview card (page header)
splashify calling analytics                      # period analytics
splashify calling calls --limit 50               # recent calls

# Templates
splashify calling templates                      # list
splashify calling template create-call-button …  # tap-to-call (URL/PHONE)
splashify calling template create-permission …   # permission request
splashify calling send <template_id> --to +91…   # send a template message

# Backend-initiated outbound call
splashify calling initiate --to +91… --phone-number-id <id>

# Recordings
splashify calling upload-recording <file> --call-id <id>

Command reference

Overview, settings, analytics, history

bash
splashify calling                                # page-header overview
splashify calling overview                       # alias
splashify calling settings                       # business hours + icon visibility
splashify calling settings update --hours … --icon-visible true
splashify calling analytics [--period 30d|7d|today]
splashify calling calls [--limit N] [--status sent|delivered|failed]
splashify calling call <call_id>                 # detail (alias: splashify call <id>)
splashify calling call <call_id> status          # latest status from backend

Templates — call buttons & permissions

WhatsApp Calling has two template variants:

bash
splashify calling templates                                       # list all
splashify calling template <template_id>                          # show one
splashify calling template create-call-button \
  --name "support_cb" --label "Call support" --phone-number +919876543210
splashify calling template create-permission \
  --name "ask_perm" --body "Tap to allow us to call you"
splashify calling template set-default <template_id>              # default for sends
splashify calling template delete <template_id>

Send a calling template message

bash
splashify calling send <template_id> --to +919876543210
splashify calling send <template_id> --to +919876543210 --vars '["John","ORD-1024"]'

Same preflight as broadcasts/email — refuses on subscription_expired or insufficient_balance before round-trip. Calling has its own per-call rate (separate from text + media).

Initiate an outbound call (AI-agent bridge)

bash
splashify call initiate --to "+919876543210"                       # uses your default voice agent
splashify call initiate --to "+919876543210" --agent-id <uuid>     # override per-call

WhatsApp Business Calling is peer-to-peer WebRTC — the business side must be a real audio peer. A shell can't be that peer, so wa-call-bridge does it for you:

  1. The handler returns 202 with a pending_call_id and queues the request on Redis wa-call:outgoing.
  2. The bridge spins up a Pion WebRTC peer, generates the SDP offer, posts to Meta /calls action=connect, gets the meta_call_id.
  3. Meta delivers the SDP answer via webhook → bridge applies it → recipient's WhatsApp rings.
  4. The bridge joins a LiveKit room named wa-call-<meta_call_id>, voice-agent-worker auto-dispatches your AI agent into the room, and the agent speaks.
bash
# Watch a call progress
PID=$(splashify call initiate --to "+919876543210" | jq -r '.pending_call_id')
while : ; do
  STATE=$(splashify call status "$PID" | jq -r '.state')
  echo "$(date -u +%H:%M:%S)  $STATE"
  case "$STATE" in
    connected|ended|failed_*) break ;;
  esac
  sleep 2
done

The state field follows this lifecycle:

AI-agent only. Outbound CLI calls always route audio to your default voice agent (or --agent-id). The CLI can't carry audio itself — there's no terminal-side mic/speaker pipe. To make a human outbound call, use the dialer at /messages.

Permission lookup

bash
splashify calling permission-status --to +919876543210      # has this user granted calling perm?
splashify calling permissions                                # all granted permissions

Upload a recording

bash
splashify calling upload-recording ./call.mp3 --call-id <call_id>

Uploads a recording to attach to a call row — useful when the audio came from a third-party PBX and you want it logged in /calling/<id>.

Common workflows

Daily call volume report

bash
splashify calling analytics --period 7d | \
  jq '.analytics.daily_series[] | {date, total: .count, failed: .failed_count}'

Re-send the default call-button template to every contact tagged "leads"

bash
TPL=$(splashify calling templates | jq -r '.templates[] | select(.is_default) | .template_id')
splashify contacts --tag leads | jq -r '.contacts[].phone_number' | \
  xargs -I{} splashify calling send "$TPL" --to {}

Watch a call ring through

See the "Initiate an outbound call" section above — the polling loop is on splashify call status <pending_call_id>, not the legacy splashify calling call <id> status.

Pause calling outside business hours

bash
splashify calling settings update \
  --hours '{"mon":["09:00","18:00"],"tue":["09:00","18:00"], … }' \
  --icon-visible true

Troubleshooting

permission_required on send — the recipient hasn't accepted the permission template. Send a --type permission template first (or check with splashify calling permission-status).

subscription_expired / insufficient_balance — same preflight errors as broadcasts/email. See Subscription and Wallet.

initiate returns 409 — no default voice agent — outbound CLI calls route audio to your AI agent. Either set a default voice agent at /ai-agents (it lights up app_users.default_voice_ai_agent_id) or pass --agent-id <uuid> on the command.

failed_no_answer after ~30s — Meta accepted the connect POST but never delivered the SDP answer webhook. Common causes: recipient hasn't granted call permission yet (check splashify calling permission-status), the WhatsApp number's calling status isn't ENABLED in the calling settings, or the call hours window is closed.

failed_meta on initiate — surface meta_error from splashify call status to see the verbatim Meta response. Most often this is a stale Interakt token or the WABA phone number isn't registered for Business Calling.

Upload-recording rejects the file — Meta accepts MP3/WAV up to ~25MB. Larger files / other formats are refused with a 400 listing the limits.

No browser dialer from the CLI — by design. CLI-initiated outbound calls always speak through your AI voice agent. To make a human outbound call, use the dialer at /messages.

See also