Cascade failover policy and logical cascade groups
A message-level cascade is a set of physical sends bound to one logical message ID: the primary send plus every fallback hop that fired when the previous hop couldn’t deliver. Orbit decides which chain a send escalates through from up to three sources, in a fixed precedence — and stamps onemessage_group_id on all of them, so the whole run is readable as a single group.
This page covers the message-level cascade model: the org default policy, the per-send override field, how a hop escalates, and how to read a finished cascade as one group. It complements Fallback on terminal DLR, which covers the per-request escalation engine these endpoints arm, and Cross-channel fallback, which covers the separate org-level capability chain. For a map of all five planes and how to choose one, see The five fallback planes. The channel send envelopes live in the Messaging API reference.
One logical ID, many legs
When a cascade resolves for a send, Orbit stamps amessage_group_id on the primary and carries the same value forward onto every hop it escalates to. Each hop is a first-class message with its own id, channel, status, timing, and billing — but the group id never changes across the chain, so the run is readable end to end as one logical message.
Read a group back with:
fallback_of: null; each hop records the message it fell back from and why (primary_<channel>_<terminal_status>), so you can reconcile “the SMS send that followed the failed RCS send” instead of seeing disconnected messages. The read re-resolves legs from your messages on each call — a partially dispatched cascade simply shows fewer legs, and an unknown group id returns an empty list rather than an error. The read carries messages:read or messages:write scope, like the rest of the messages surface.
The org default policy
Use the policy endpoints when you want every send to escalate through the same chain — SMS → WhatsApp → RCS, or any ordered chain of rich-message channels — without stamping metadata per request. Read the current policy:configured: false (with a null policy) means no policy has been set — not that cascade is disabled.
Replace the policy:
The eligible channel set is closed:
sms, whatsapp, rcs, viber, telegram, messenger, instagram, line, apple_messages. Voice, fax, email, and push are never cascade-eligible — their billing and consent semantics differ too much for an automatic hop (an undelivered email turning into SMS is a compliance surface, not a convenience). Validation rejects anything outside the set with a 422 that names each invalid field. A malformed policy stored by hand — for example, from a manual settings edit — degrades to configured: false on read rather than failing sends.
The policy stores on the organization itself under messaging settings — there is no separate table to migrate — and the update writes against the single settings key, so sibling messaging settings are never clobbered. Writes carry messages:write scope plus an administrator-or-higher role, and each update records an audit-log entry.
Per-send override
Two sources can supply a chain on a single send, and they beat the org policy in a fixed precedence — rawmetadata.fallback_channels first, then the typed cascade field, then the org policy. The first non-empty provider wins; sources are never merged, so a caller’s own chain is never silently over-filed by the org default.
Stamp a chain with the typed field on the send body (present on every channel send endpoint):
enabled, channels, fallback_window_seconds) and defaults to enabled: true. To opt a single send out of the org policy:
metadata.auto_fallback: false even when a chain is present, and the legacy raw-metadata path (metadata.fallback_channels stamped by your pipeline) always wins over both the typed field and the org policy. Only cascaded sends carry a message_group_id — a normal single-channel send groups to nothing, so you can filter “this recipient cascaded” cleanly.
How a hop escalates
When a chain resolves, the send stamps the fallback metadata exactly once, at send time:fallback_channels— the ordered chain (deduped, with the primary channel removed so a chain never cascades into itself).auto_fallback: true— the explicit opt-in the escalation engine requires.fallback_window_seconds— the freshness cap, when set.message_group_id— generated once per cascade; never re-generated mid-chain.
failed, undelivered, or rejected receipt lands within the window, the engine dispatches the next channel as a brand-new send, carries the remaining tail of the chain and the group id forward, and stamps fallback_of plus the terminal status as the reason on the incoming hop. A hop-bounding idempotency stamp on the original row guarantees at most one dispatch per message regardless of carrier webhook replays.
Crucially, every hop re-enters the canonical send pipeline, so sender resolution, opt-out checks, and quiet-hours/consent gates are re-applied per hop — the recipient’s SMS opt-out blocks an SMS hop even if they accepted RCS, exactly like Cross-channel fallback specifies. Each hop bills on its own channel; there is no separate cascade fee class.
That reroute is the legitimate end of the failing leg on the status ladder: undelivered and failed do not close the logical message — the fallback hop re-enters queued as a fresh message under the same message_group_id, with full queue visibility on the Delivery Log, instead of leaving the failed row as the end of the run. A row that terminates on failed or undelivered without a chain is a genuine close, not a stall the fallback could have rescued — the ladder distinguishes the two.
Org-policy reads on the send path are non-fatal: a transient org-settings failure never blocks a billed send — the send proceeds with only its declared (or empty) chain.
Reading a finished cascade
Two signals distinguish a complete group from a partial one:- The groups endpoint.
GET /messages/cascade/groups/:groupIdreturns every persisted leg, bounded at 100, ordered by send time. A leg that failed to dispatch is simply absent — the shape tells you exactly which hops ran. - The escalation-past-primary signal. Any leg whose
fallback_ofis non-null tells you the primary did not deliver and the chain advanced; pairingfallback_ofwith each leg’s finalstatustells you where the run ended — adeliveredterminal hop closes it, while a terminal status on the last leg means the chain was exhausted.
Interaction with the notify composer
ThePOST /notify endpoint composes per-recipient cascades in a single call — mode: "waterfall" plus fallback_window_seconds — and the dashboard’s Multi-channel notify composer builds those chains visually. That surface (covered in Cascade fallback chains in the notify composer) is unrelated to the endpoints on this page, which govern message-level cascades and the org-level default every single-channel send resolves against. Use the notify composer when you send one body to a mixed recipient list; use the policy and per-send field when you want cascade behavior attached to ordinary messaging sends.
Related
- Fallback on terminal DLR — the escalation engine the cascade policy arms.
- Cross-channel fallback — the org-level capability chain — a separate, earlier-resolving fallback plane.
- Message envelope model — the per-send
channel_chainrouting-by-choice declaration that is explicitly not one of these cascades. - Cascade fallback chains in the notify composer — per-recipient waterfall chains in one notify call.
- Message status DAG — which statuses are terminal and therefore trigger escalation.
- Messaging API reference — channel send envelopes the
cascadefield rides on.