Skip to main content

Fallback configuration worked samples

The concept pages define Orbit’s fallback planes; this page gives you the exact payloads to arm them. Every sample is a tenant-owned control: the chain order, the escalation window, and the cost ceilings are choices you make per request or per organization. Copy a request as written, substitute your own numbers and key, and compare the envelope you get back. The two read-back shapes and the five planes they sit on are defined in The five fallback planes and Cascade failover policy and groups — read those for precedence rules; come here for the wire shape. The visual composer over POST /notify is covered in Cascade fallback chains in the notify composer.

The primitives, mapped to fields

Four primitives, each a field you send: An escalation trigger is always a terminal delivery receipt — failed, undelivered, or rejected — arriving inside the window. There is no timed retry without a receipt, and each hop runs at most once: a receipt replayed by the carrier cannot re-send a hop.

Worked sample 1: SMS backup after a WhatsApp primary

One recipient, one message, two hops. Order the bindings richest → cheapest; the first binding is the primary, the rest form the escalation tail.
In waterfall mode only the primary dispatches now; the remaining results entries are registered as armed hops rather than sent. If the WhatsApp hop returns a terminal receipt within 3600 seconds, the SMS hop fires as a brand-new send. fallback_window_seconds accepts 30 to 86,400 seconds (24 hours maximum) on this endpoint; omit it for the 24-hour engine default. Waterfall requires at least two bindings — a single-binding waterfall fails validation with a 400 naming bindings.

Worked sample 2: Cost-bounded three-hop chain

max_price bounds each hop independently; max_total_price bounds the whole chase. Here RCS prices above its per-hop ceiling of 0.02 and is rejected before billing if armed, and the cumulative cap trims whatever would push the past-costly tail over 0.05:
A trimmed tail is never attempted, and the read-back receipt (below) names it in dropped_for_cost_cap. Per-binding max_price overrides the shared value; max_total_price itself is ignored in the default fanout mode, where every binding fires immediately and there is no cascade to cap.

Worked sample 3: One org default for every send

When you want the same chain on every single-channel send without stamping fields per request, set the org policy once:
Read it back with GET /api/v1/messages/cascade/policy:
An org that has never set one returns { "data": { "policy": null, "configured": false } }. Policy writes require the messages:write scope and an owner, administrator, or developer role, and each write records an audit-log entry. Senders keep their own chain: a per-send cascade field or a raw metadata.fallback_channels value beats the policy — sources are never merged.

Worked sample 4: Per-send override and opt-out

Override the org policy for one send, or opt a send out of it entirely:
The typed cascade field accepts the same shape as the policy and defaults to enabled: true. Declaring a chain with "enabled": false — or setting raw metadata.auto_fallback: false — opts that one send out even when a chain exists; the resolution reads the false guard before it stamps anything.

Reading a cascade back

Two read-back endpoints close the loop. The /notify receipt aggregates every hop of one envelope into a single status and a unified price:
status is delivered when any hop landed, failed when the last hop terminated with nothing left armed, and in_progress while the cascade is live. The cost.total sums only the hops that actually fired — the armed-but-unsent tail is never billed. When a max_total_price trimmed the chain, dropped_for_cost_cap names the channels that never ran. For policy-driven or per-send cascades, every hop shares one message_group_id, returned on send and readable as a group:
An unknown group id returns an empty legs list, not an error. A leg that failed to dispatch is simply absent, so the shape always reflects which hops actually ran. Every hop is a first-class new send, so the same gates apply at every hop of every plane:
  • Opt-out and consent. The recipient’s SMS opt-out blocks an SMS hop even if the primary on another channel was accepted. TCPA re-checks run on every hop into SMS.
  • Quiet hours. Your tenant-configured quiet-hours windows gate each hop independently — a cascade armed during a quiet window holds the hop until the window opens, and the freshness window bound applies hop by hop.
  • Per-hop billing. Each hop prices and charges on the channel it lands on; there is no fallback or cascade fee class.
Because gates re-run per hop, a hop that fails a gate stops the chain exactly as a terminal receipt would.

The closed channel set

The eligible chain set for the cascade policy and the per-send cascade field is fixed: sms, whatsapp, rcs, viber, telegram, messenger, instagram, line, apple_messages. Voice, fax, email, and push are never cascade-eligible on the messaging endpoints — an undelivered email or call re-arming as a text is a compliance surface, not a convenience. A policy or cascade chain naming one of them fails validation with a 422 listing the rejected fields; the escalation engine additionally refuses hops into those channels even when a raw metadata.fallback_channels chain names them. POST /notify in the default fan-out mode accepts the full channel set, because there every channel fires once, immediately, by explicit declaration — it is the waterfall escalation into voice/email that is closed. Voice-specific fallback chains exist only on the verify product surface (see Verify fallback chains), not on messaging sends.