> ## Documentation Index
> Fetch the complete documentation index at: https://docs.orbit.devotel.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Fallback chains: pick and configure channel cascades across campaigns, single messages, and OTP

> One task-level guide to every fallback surface in Orbit — the campaign channels[] ladder, the per-message cascade_policy, and Verify profile step-up chains — with ordering rules, worked examples, failure semantics, per-hop costs, and preview-before-arming.

# Fallback chains: pick and configure channel cascades across messaging and OTP

A **fallback chain** is an ordered list of channels your outbound traffic tries in sequence: when one channel cannot deliver (or the recipient never engages), the traffic moves to the next channel in the list instead of dying. Orbit has three places you can build such a chain, and this guide is the one page that walks through all three and tells you which to use:

1. **Campaign-level chains** — the `channels[]` array on a campaign (`failed` / `no_delivery` / `no_engagement` triggers + a `fallback_after_seconds` window per non-final entry). Best for marketing blasts, drips, and journeys. Covered in the [campaign end-to-end guide](/guides/campaign-end-to-end) under "Channel fallback ladder".
2. **Per-message cascade policy** — a `cascade_policy` object on `POST /messages/smart-send` (or read-only on `POST /messages/route-preview`) that resolves an explicit or router-suggested fallback set and stamps it onto the message metadata as `fallback_channels`. Best for transactional traffic. Full detail in the [smart-send fallback chains guide](/guides/smart-send-fallback-chains):

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/messages/smart-send" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "to": "+14155552671",
    "body": "Your order #8421 shipped — track it here.",
    "message_type": "transactional",
    "rcs_available": true,
    "whatsapp_available": true,
    "cascade_policy": {
      "fallback_channels": ["whatsapp", "sms"],
      "segment": { "campaign_id": "cmp_retail_q3" }
    }
  }'
```

The 202 response echoes the resolved chain so you can confirm what got stamped:

```json theme={null}
{
  "data": {
    "id": "msg_01H...",
    "channel": "rcs",
    "status": "sent",
    "cascade": {
      "primary": "rcs",
      "fallback_channels": ["whatsapp", "sms"],
      "ordering": "static"
    }
  }
}
```

If the `rcs` primary comes back `undelivered` on the terminal DLR, the escalation hook fires a new send on `whatsapp` carrying the `["sms"]` tail, and so on until the tail empties.

3. **Verify profile step-up chains** — the ordered `channels` array on a [Verify profile](/guides/verify-fallback-chains) (`sms → whatsapp → voice`, and so on) that a background engine advances when the previous channel cannot deliver the code. Best for OTP and MFA.

The two concept pages — [Cross-channel fallback](/concepts/cross-channel-fallback) (org-level reachability chain) and [Fallback on terminal DLR](/concepts/multi-channel-dlr-fallback) (post-DLR per-request escalation) — describe the underlying engines the surfaces above compose with. You do not need to read them to configure any of the three; they explain what happens when the surfaces run.

## 1. Choose the chain type per use case

| Use case | Surface | Where you configure it |
| - | - | - |
| Marketing blast / bulk send (weakest-to-strongest channel order across the audience) | Campaign `channels[]` ladder | On the campaign, at create/update time |
| One transactional message where you need a fallback NOW (RCP-capability-uncertain sends, OTP-first SMS fallbacks, retry-on-the-fly) | `cascade_policy` on smart-send / route-preview | On each `/messages/smart-send` or `/messages/route-preview` call |
| OTP / MFA delivery (SMS → WhatsApp → voice step-up) | Verify profile `channels[]` | On a Verify profile; `profile_id` passed per send |
| Org-wide default chain (e.g. every RCS send falls back to SMS when the device has no RCS) | Org chain | Settings → Channels → Cross-Channel Fallback (concept page: [Cross-channel fallback](/concepts/cross-channel-fallback)) |

Rule of thumb: campaigns for marketing, `cascade_policy` for one-shot transactional, Verify profiles for OTP. They compose — a campaign can roll out its `channels[]` ladder for the bulk, while each sent message inside the campaign can additionally opt into per-message escalation; only opt into the per-message shape when the org chain would not already cover you.

## 2. Channel-ordering rules (all three surfaces)

* **First entry = the primary channel.** For campaigns the **top-level `channel` must equal `channels[0].channel`**. For Verify profiles the send body's `channel` should equal `channels[0]` (a different one is tacked on front, not an error). For `cascade_policy` the smart router picks the primary and the policy shapes only the fallback set — never the primary.
* **The terminal entry carries no fallback fields.** In a campaign chain, the last object in `channels[]` must not carry `fallback_on` / `fallback_after_seconds` — it is the hop that finishes the chain. The platform enforces this at validation; a chain whose last entry still declares a fallback trigger is rejected with a 422 naming the entry.
* **Channel-specific fields live only on that channel's entry.** `whatsapp_template` is legal only on a `channel: "whatsapp"` entry — never on the `sms` or primary entry of a mixed chain. (Same rule holds for `rcs_template`, `subject` on email entries, etc. — see the campaign guide's field table.)
* **Duplicate channels are rejected.** Depth and no-duplicate rules are enforced at create/update time (campaigns) and profile save time (Verify).
* **Messaging-only entries** in per-message cascades: `voice` and `fax` are filtered out of `cascade_policy.fallback_channels`; Verify chains additionally admit `voice` (with a `templates.voice` script containing `{{code}}` — mandatory) plus `email`, `viber`, `telegram`, `rcs`, `flashcall`.

## 3. Worked example A — WhatsApp-first → SMS (campaign chain)

The shape you reach for on a marketing promo: charge the richer channel first, fall back to the universal one on terminal failure. Reuse the pattern from the [WhatsApp campaign launch playbook](/guides/whatsapp/campaign-launch-playbook):

```json theme={null}
{
  "channel": "whatsapp",
  "channels": [
    {
      "channel": "whatsapp",
      "order": 0,
      "fallback_on": "failed",
      "fallback_after_seconds": 300,
      "whatsapp_template": { "name": "summer_drop_vip_v2", "language": "en" }
    },
    { "channel": "sms", "order": 1 }
  ],
  "name": "Retail promo — summer drop",
  "type": "blast",
  "audience_type": "list",
  "audience_id": "list_summerVIPs",
  "message_template": "Hi {{first_name}}, early access starts now: 25% off through Sunday. Reply STOP to opt out."
}
```

`fallback_on: "failed"` advances on a terminal DLR (`failed` / `undelivered` / `rejected`); `fallback_on: "no_delivery"` advances when no DLR arrives within `fallback_after_seconds`; `fallback_on: "no_engagement"` advances when delivery succeeded but no engagement event (open / read / reply) was recorded in the window — the SMS hop still costs a send. The last `sms` object deliberately carries no fallback fields: it is the terminal hop.

## 4. Worked example B — OTP step-up (Verify profile)

An SMS → WhatsApp → voice chain; the same code is re-delivered on each hop, and `voice` requires a `templates.voice` script with `{{code}}`:

```bash theme={null}
curl -X POST "https://api.orbit.devotel.io/api/v1/verify/profiles" \
  -H "X-API-Key: $ORBIT_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Login OTP",
    "channels": ["sms", "whatsapp", "voice"],
    "codeLength": 6,
    "expirySeconds": 600,
    "maxAttempts": 3,
    "rateLimitPerHour": 5,
    "templates": {
      "voice": "Your {{app_name}} security code is {{code}}. I repeat, {{code}}."
    }
  }'
```

Pass the returned `profile_id` on `/verify/send`; the initial send tries `sms`, and a background engine advances to `whatsapp`, then `voice`, on a delivery failure or a `channel_timeout_seconds` timeout (10–600 s, default 60 s) — see the [Verify fallback chains guide](/guides/verify-fallback-chains) for the advance engine and the `verification.fallback_triggered` / `verification.fallback_exhausted` events.

## 5. Failure semantics: what counts as `no_delivery` vs `no_engagement`, and when the chain stops

* **`failed`** (campaign chains): a terminal DLR on the current hop — the carrier replied with `failed`, `undelivered`, or `rejected`. Cheapest trigger: the chain moves only when a hard failure is known.
* **`no_delivery`** (campaign chains, and the Verify advance engine's timeout branch): no terminal DLR arrived within `fallback_after_seconds` (campaign) or `channel_timeout_seconds` (Verify). Use it for channels whose carriers routinely leave you hanging, or for WeChat where template sends may never produce a delivery callback.
* **`no_engagement`** (campaign chains only): the message was delivered but produced no open/read/reply event within the window. This hop costs a second send and should be used sparingly — measure the engagement criteria carefully before enabling it.
* **Verify profiles** advance on either a delivery failure or the timeout (above). `no_engagement` does not apply to OTP: a delivered code that is not yet approved is not a deliverable-channel failure.
* The chain stops when a hop succeeds, when the terminal hop is reached, or (Verify) when every hop has failed (`verification.fallback_exhausted` fires). Every hop's outcome is attributed on its DLR webhook under its own message id (per-message cascades / campaign chains) or the `verification.fallback_triggered` event (Verify), so a per-hop attribution read always shows the final outcome per hop, not one aggregated status.
* A per-message fallback **never re-arms an expired message**: `fallback_window_seconds` (default 24 h) caps how fresh the original send must be for a terminal DLR to escalate (see [Fallback on terminal DLR](/concepts/multi-channel-dlr-fallback)).

## 6. Cost: per-hop billing, and cap the whole thing

Every hop is billed independently — the cascade engine treats a fallback advance as a new send on the next channel, and the wallet deducts per hop as the chain advances (for campaigns the per-hop cost comes from the same resolver the dry-run uses; for Verify the channel's per-message rate applies; for per-message cascades the route-preview cost gates apply).

Two bounds you should set explicitly when a chain can grow expensive — documented in [Notify cascade cost model](/concepts/notify-cascade-cost-model):

* **`max_price`** (per-binding or request-level): reject a single hop whose projected price exceeds the cap before billing it — a pricey RCS hop can be skipped without burning the SMS tail.
* **`max_total_price`**: a cumulative budget across the whole waterfall — the chain is trimmed at arm time so the running total of kept hops never exceeds your declared cap.

For campaign chains, set `credit_cap_usd_cents` on the campaign as a hard spending cap; for Verify profiles, `rateLimitPerHour` bounds per-recipient spend velocity.

## 7. Preview a chain before arming it

Two read-only preview endpoints let you check what a chain would do without sending:

* **`POST /messages/route-preview`** — resolves the smart router's primary pick and your `cascade_policy` (explicit or router-suggested) and returns the resolved chain under a `cascade` block with the `ordering` (`static` / `adaptive` / `off`). Total cost estimate included. See the [smart route preview guide](/guides/smart-route-preview).
* **`POST /campaigns/:id/preview-smart-send`** — on a `send_time_optimization: "recipient-optimal"` campaign, returns per-recipient recommended times plus a `next_best_channel` hint so you can see what channel each recipient would land on before the bulk send commits. See the [campaign end-to-end guide](/guides/campaign-end-to-end) step 6.

For Verify profiles the "preview" is to subscribe to `verification.fallback_triggered` and run a manual `/verify/send` against a test recipient before wiring the profile into your login flow.

## 8. Where each surface links next

* Campaign chains: [Send a campaign end-to-end](/guides/campaign-end-to-end) (step 6 "Channel fallback ladder") and the [WhatsApp campaign launch playbook](/guides/whatsapp/campaign-launch-playbook).
* Per-message cascades: [Smart-send fallback chains](/guides/smart-send-fallback-chains) and [Smart route preview](/guides/smart-route-preview).
* Verify profiles: [Verify fallback chains and advanced factors](/guides/verify-fallback-chains).
* Underlying concepts: [Cross-channel fallback](/concepts/cross-channel-fallback), [Fallback on terminal DLR](/concepts/multi-channel-dlr-fallback), [Notify cascade cost model](/concepts/notify-cascade-cost-model), [Cascade groups and planes](/concepts/fallback-and-cascade-planes).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.