> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://docs.voicebip.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://docs.voicebip.com/_mcp/server.

# Multi-Channel Cascade

> Reach a contact by voice; if they don't pick up, fall back to SMS, then WhatsApp — automatically. The cross-channel outreach neither voice-only tool can do.

**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.

> **Note**
>
> **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](/use-cases/campaigns-deep-dive) primitives.

## Prerequisites

* A voice [agent](/use-cases/inbound-support-kb) for the call rung.
* An approved WhatsApp template for the WhatsApp rung (see [WhatsApp AI agent](/use-cases/whatsapp-agent)).
* `ENABLE_CASCADE` enabled on your workspace, plus the outbound flags.
* Familiarity with [Campaigns](/use-cases/campaigns-deep-dive) — 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):

```bash
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.

> **Tip**
>
> 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](/use-cases/campaigns-deep-dive) for the target schema):

```bash
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`:

```json
{ "event_type": "message.delivered", "payload": { "campaign_id": "camp_9k…", "to_number": "+2348031234567" } }
{ "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.

> **Note**
>
> 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:

```bash
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

| Symptom                                                        | Likely cause                                                                                                                                             |
| -------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `campaign channel must equal the first cascade rung` on create | The campaign's `channel` doesn't match `ladder[0].channel` — they must be the same entry channel.                                                        |
| `invalid cascade ladder` on create                             | Unknown channel, negative `wait_seconds`, or more than 6 rungs.                                                                                          |
| Ladder escalates even though they replied                      | The "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 triggers                                         | `ENABLE_CASCADE` not set on the workspace, or no `cascade` block in `config`.                                                                            |
| WhatsApp rung fails                                            | Template not approved, or its category missing from `config`.                                                                                            |

## Next steps

* The campaign lifecycle and counters underneath cascades → [Campaigns deep-dive](/use-cases/campaigns-deep-dive).
* Make the voice rung smarter with tools → [Tool-calling](/use-cases/tool-calling).
* Single-channel version → [Outbound reminder campaign](/use-cases/reminder-campaign).