Cascade (waterfall) fallback chains in the notify composer
POST /api/v1/notify sends one body to a mixed list of recipients across SMS, WhatsApp, email, RCS, push, Viber, Telegram, and Messenger in a single API call. The Messages → Multi-channel notify page in the dashboard is a visual composer over that endpoint, so you can build the recipient list and pick how it delivers.
This guide covers the two delivery strategies the composer offers, how cascade groups your rows into a per-recipient fallback chain, how to bound the escalation window, and how to read what came back. Everything here is a tenant-owned control — the chain order and the window are choices you make per message.
When to pass a mode — fan-out vs waterfall
The composer maps onto two values of themode field:
- Fan-out (default). Every binding sends immediately, in parallel. One delivery attempt per binding; there is no retry-across-channels. Use it for general announcements where one channel — maybe two for redundancy — is enough and you want the message out everywhere at once.
- Cascade (
waterfall). Only the first binding fires now. The rest ride along as an ordered fallback chain, and the delivery engine advances to the next hop only when the active hop reports back undelivered inside the freshness window. Use it for time-critical messages (a one-time passcode, an appointment reminder, an outage alert) where the cheapest reliable channel should win and the expensive ones are a safety net, not a scatter-blast.
rcs → whatsapp → sms → email). The binding order in your request is the escalation order. Omit mode and the API treats it as fanout.
Build a cascade in the composer
- Open Messages → Multi-channel notify and click Multi-channel notify to open the composer.
- Set the Delivery strategy to Cascade (waterfall). The composer relabels the recipient list to “Recipients & chain”.
- Enter one recipient per row (channel + address). To give a recipient a fallback chain, enter the same address on two or more rows with different channels — rows that share an address merge into one chain. The leftmost column numbers each row so the escalation order is visible.
- (Optional) Set the Escalation window (seconds). This bounds how late an undelivered delivery receipt (DLR) may still trigger the next hop: minimum 30 seconds, maximum 24 hours. Leave it blank to use the 24-hour default. A value outside that range is dropped when the payload is built, so a mistyped window never blocks your send — and the API would clamp it to the floor/ceiling anyway.
- Read the per-recipient chain preview under the list. Well-formed chains (2+ hops) render as
EMAIL → SMS → WHATSAPP. A recipient entered once (or a duplicated address with only one channel) shows an amber “Add another channel for failover” callout, and the composer blocks the send with an error naming the offending address while you fix it. - Finish writing the body, optionally a sender, and click Start cascade.
The single-hop guard
Cascade without a second hop degrades silently to fan-out semantics: the first hop fires and there is nothing left to escalate to. The composer therefore requires at least two channels per recipient in waterfall mode and blocks the send otherwise. (The API’s own refine only requires ≥2 bindings total for waterfall mode — the per-recipient check here is stronger and lives in the composer.) When you build payloads in code rather than the composer, apply the same rule yourself.Fan-out send — worked request and response
Fan-out is the default, so you can omitmode entirely. This call targets three channels on one address:
message_id):
error:
status: keep the queued rows, and retry or rebuild only the failed ones. Never re-POST the whole batch — that double-sends the rows that already went out.
Cascade send — worked request and response
Passmode: "waterfall" and bind the escalation window explicitly. Only hop 1 fires on this call; hops 2–3 arm and wait:
active is the hop that fired, skipped lists any bindings rejected synchronously before the active hop queued, and fallback_chain is the armed tail in escalation order:
mode defaults to fanout when omitted; fallback_window_seconds accepts 30–86400 and defaults to 86400 (24h). Every hop still runs through the messaging router — sender resolution, opt-out, and compliance checks apply per hop, matching each destination’s reachability rules. The full field specification (per-binding overrides, shared template_name / template_params, response envelope) is in the /notify endpoint reference.
SDK variants — the same cascade in code
/notify has no typed SDK helper yet, so both SDKs go through the low-level request helper (the same auth, retry, and error model as the typed methods). Both read ORBIT_API_KEY from the environment:
waterfall mode, pass at least two bindings per recipient, and keep fallback_window_seconds in the 30–86400 band. The API refines a whole-request single-binding cascading payload with 422, but the per-recipient “two channels minimum” rule lives in the composer — enforce it yourself on this path.
Errors
A queued envelope can still have rejected hops inside it, and the failure surface splits across three codes:- 400 (validation envelope) — a malformed request never reaches the wire:
modenot one offanout/waterfall, afallback_window_secondsoutside 30–86400, a waterfall with fewer than two bindings, or a body with nobody/template_nameanywhere. The JSONerror.detailsnames the offending field. - 207 (fan-out only) — partial per-row failure. The
results[]row carrieserror.code— a per-channel rejection such asRECIPIENT_OPTED_OUT, a bad address, or a missing sender — while the other rows queued. - 422 — nothing queued: every binding failed in fan-out, or every binding rejected synchronously in waterfall. Rebuild the payload and resend; the batch is safe to retry because nothing went out.
error.code values above rather than as blanket failures.
Reading the result
The screen tells you whether each row queued and whether the strategy badge says Fan-out or Cascade:- 200 OK — every binding succeeded (fan-out), or at least one binding queued (waterfall). The row list shows a per-row chip: Queued (fan-out) — it fired now; Queued (waterfall) — this hop is the one that fires first, and any delayed deliveries for still-pending hops have not happened yet.
- 207 Multi-Status (fan-out only) — some rows queued and some failed. The row chips show Failed with a human-readable reason (opt-out, quota, bad address); hover a row to see the machine-readable code. Branch per row — never blanket-retry the batch.
- 422 — in fan-out every binding failed; in waterfall every binding was rejected synchronously. Nothing was queued; fix the body/addresses and try again.
The dashboard badge “cascade queued — only the first hop fired” means exactly that. A queued row next to an overwrite from an earlier run is fine; treat delivery (confirmed DLR) as the terminal signal, and use fan-out when you cannot tolerate an ambiguous queued-only result.
When to use which — a decision shortcut
After you send, resolve the resulting envelope by its notify id — hop chain, per-hop cost, and the escalation flag — with the Track a cascade panel.
See also
- Track a live cascade by notify id — resolve the hop chain, billed cost, and escalation flag after you send.
- /notify endpoint reference — full request/response specification for both strategies.
- Verify fallback chains — ordered channel fallbacks for OTP verification, as a reusable profile rather than a per-message chain.