Multi-Channel Cascade

What you build: an outreach flow that tries channels in order until the person responds. Message on WhatsApp first; if no reply, send an SMS; if still nothing, place a call. Each “rung” only fires if the previous one didn’t reach them — so you send only what’s needed. (The order is yours; WhatsApp → SMS → voice is the default.)

Target: +2348031234567 (default ladder: WhatsApp → SMS → voice)
Rung 1 WhatsApp ── reply within 60m? ──▶ done
│ no response
Rung 2 SMS ── reply within 30m? ──▶ done
│ no response
Rung 3 voice call ── answered? ──▶ done (else exhausted)

Who it’s for: collections, high-value appointment reminders, urgent notifications — anywhere reaching the person matters more than the channel. This is a genuine Voicebip differentiator: voice-only platforms can’t cascade across SMS and WhatsApp.

Where the cascade is configured. Cascade is an orchestrator feature behind the ENABLE_CASCADE flag. You turn a normal campaign into a cascade by adding a cascade.ladder block to the campaign’s config. The ladder is an ordered list of rungs, each just a channel and a wait window. The campaign’s own channel must equal the first rung (the entry channel). The lifecycle, targets, and counters underneath are the ordinary Campaigns primitives.

Prerequisites

  • A voice agent for the call rung.
  • An approved WhatsApp template for the WhatsApp rung (see WhatsApp AI agent).
  • ENABLE_CASCADE enabled on your workspace, plus the outbound flags.
  • Familiarity with Campaigns — a cascade is a campaign whose config carries an ordered set of channel steps.

Step 1 — Define the cascade

Create a campaign whose config carries a cascade.ladder. Each rung is exactly two fields — channel and wait_seconds (how long to wait for a response before escalating to the next rung):

$curl -X POST https://api.voicebip.com/v1/campaigns \
> -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
> -d '{
> "channel": "whatsapp",
> "agent_id": "agt_collections_…",
> "config": {
> "from_number": "+2341002223344",
> "sms_body": "Hi {{name}}, your payment of {{amount}} is overdue. Reply PAY to arrange.",
> "wa_template_name": "payment_overdue",
> "cascade": {
> "ladder": [
> { "channel": "whatsapp", "wait_seconds": 3600 },
> { "channel": "sms", "wait_seconds": 1800 },
> { "channel": "voice", "wait_seconds": 0 }
> ]
> }
> }
> }'

How the ladder works:

  • The rung only picks the channel + wait. The message content per channel (SMS body, WhatsApp template + category, from_number) comes from the campaign config and the target’s params — not from the rung.
  • The campaign’s channel must equal the first rung’s channel (the ladder’s entry channel — the campaign seeds onto that channel’s dispatcher). Here both are whatsapp.
  • Stop condition is “the target responded” — a WhatsApp reply or an answered call. There is no per-rung advance_on: before each rung a re-check reads a per-(workspace, number) “reached” marker, and a target who responded is never escalated. Otherwise, after wait_seconds with no response, the sweeper advances to the next rung.
  • wait_seconds ≥ 0 (0 = escalate immediately / final rung). Max 6 rungs. The last rung’s wait is unused.

Opt into the default ladder — WhatsApp (60m) → SMS (30m) → voice — by sending an empty block: "config": { "cascade": {} }. Then the campaign channel must be whatsapp.

Step 2 — Add targets and start

Targets carry the params every rung references (name, amount), so all channels stay personalised (see Campaigns deep-dive for the target schema):

$curl -X POST https://api.voicebip.com/v1/campaigns/camp_9k…/targets \
> -H "Authorization: Bearer pk_xxx" -H "Content-Type: application/json" \
> -d '{ "targets": [ { "to_address": "+2348031234567", "params": { "name": "Ada", "amount": "₦12,500" } } ] }'
$
$curl -X POST https://api.voicebip.com/v1/campaigns/camp_9k…/start -H "Authorization: Bearer pk_xxx"

Step 3 — Track which rung reached them

Each rung emits its channel’s terminal webhook with the campaign_id. A WhatsApp reply or an answered call sets the “reached” marker and stops escalation; anything else lets the ladder advance after the rung’s wait_seconds:

1{ "event_type": "message.delivered", "payload": { "campaign_id": "camp_9k…", "to_number": "+2348031234567" } }
2{ "event_type": "call.completed", "payload": { "campaign_id": "camp_9k…", "status": "completed" } }

Double-contact protection (handled for you)

The dispatcher marks each target “in-flight” as a crash-idempotency guard, keyed by the bare target id. A cascade walks one target through several rungs on that same key, so a previous rung’s stale marker could make the next rung get skipped as “already in-flight.” The sweeper handles this automatically:

  • Non-voice prior rung (SMS/WhatsApp): its marker only clears on a carrier delivery receipt, which can be slow or never arrive — so the sweeper force-clears it before dispatching the next rung. Voice-after-SMS works.
  • Voice prior rung: a voice marker clears reliably on call.completed and is a live-resource guard (if the prior call is still ringing when the window elapses, the marker legitimately blocks a double-dial). The sweeper leaves voice markers alone.

So you can order rungs however you like, including the default WhatsApp → SMS → voice. Once-per-rung idempotency is guaranteed separately by an atomic step-advance, not the marker.

If you ever see a target that reached neither a response nor ladder-exhaustion, check the orchestrator logs for clear stale in-flight marker failed — that’s the one path where a Redis error could leave a rung skipped.

Test it (sandbox)

Run the cascade with pk_test_ targets (+234800000xxxx). Synthetic dispositions let you rehearse the rung progression with ₦0 billing. Simulate the first rung’s outcome to drive the ladder:

$curl -X POST https://api.voicebip.com/v1/webhooks/test \
> -H "Authorization: Bearer pk_test_xxx" -H "Content-Type: application/json" \
> -d '{ "webhook_url": "https://you.example.com/webhooks", "event_type": "call.completed" }'

Troubleshooting

SymptomLikely cause
campaign channel must equal the first cascade rung on createThe campaign’s channel doesn’t match ladder[0].channel — they must be the same entry channel.
invalid cascade ladder on createUnknown channel, negative wait_seconds, or more than 6 rungs.
Ladder escalates even though they repliedThe “reached” marker is per (workspace, number) with a TTL of the reach budget — a reply after it expired won’t stop a later rung. Keep windows tight.
Cascade never triggersENABLE_CASCADE not set on the workspace, or no cascade block in config.
WhatsApp rung failsTemplate not approved, or its category missing from config.

Next steps