API recipe: voice broadcast with SMS fallback
This page is the sample pack for the voice → SMS cascade: one POST that leads with a voice call and catches the undelivered cohort on SMS, the receipts that show each hop’s outcome, the sandbox run that proves the advance before real carriers are involved, and the SDK calls that page the hop messages. It condenses the voice broadcast with SMS fallback walkthrough and the outbound broadcast setup guide into copy-paste request/response pairs. Use this page when you want the raw HTTP shapes. For the why (why voice never rides a messaging cascade policy, TCPA and DNC gates, the Inbox reply loop), read the walkthrough. For the concept of tracking a cascade hop by hop, read track a cascade.1. One-path voice → SMS cascade receipts
The chain body
The surface that starts on voice isPOST /api/v1/notify in mode: "waterfall": the recipient crosses two bindings[], binding order is the chain order, and hop 0 fires immediately. The messaging cascade policy (PUT /api/v1/messages/cascade/policy) carries an ordered channels array for SMS → WhatsApp → RCS style chains, but the resolver filters voice out of every stamped chain, so a voice-first cascade is always a /notify waterfall:
cascade_policy is the org default for messaging channels, the waterfall bindings[] is the explicit per-recipient chain that may lead with voice.
The waterfall body — voice is hop 0 (fires now), SMS is hop 1 (armed; fires only on a terminal voice failure inside the window):
The failed hop’s DLR, then the second hop
Poll the receipt until the voice hop terminates. Ano-answer inside the window fires the SMS hop; the receipt then carries both hops, each with its own status, price, and error code:
status names the cascade outcome, delivered_channel names which hop landed it. Only attempted hops bill; cost.total is the true cost of reaching this recipient.
cURL, Node, Python
waterfall mode requires at least 2 bindings): a chain with one hop has nothing to fall back to. mode: "fanout" fires both bindings at once and is the wrong shape for a fallback.
2. Track the cascade
Two GETs cover the routine.GET /api/v1/notify/:notifyId returns the envelope above; GET /api/v1/messages/cascade/groups/:groupId resolves every leg of one logical cascade from the message rows, ordered primary → newest hop, for sends that carry a message_group_id:
Poll
GET /api/v1/notify/:notifyId on a backoff (5s, 15s, 60s) until status leaves in_progress; a cascade reports failed only when every hop terminated non-delivered and no tail is armed. The dashboard’s Track a cascade panel resolves the same envelope; track a cascade covers every hop column.
3. Sandbox verify: failed primary, passed fallback
A sandbox key (dv_test_sk_*) turns each recipient’s trailing digit into a deterministic receipt, per the sandbox magic numbers table. The two digits that prove this recipe end to end: a voice hop to a …3 number reports undelivered (the escalation fires), and an SMS hop to a …2 number reports delivered. Point the voice binding at …3 and the SMS binding at …2 and re-send the body from section 1:
hop index plus the one field that proves the outcome (error_code for the failed primary, delivered for the passed fallback). grep -Ezo lets the pattern span the hop object’s newlines; in CI, exit non-zero on a missing match and the sandbox run gates the deploy of your send script. The sandbox simulates receipts only; it never rewrites your chain, so what advances in sandbox advances in production.
4. SDK: page the cascade’s hop messages
The hopmessage_ids from the receipt are ordinary message rows, so the SDK message list pages them. Filter to the SMS hop and follow the cursor; match rows back to a cascade by the ids the receipt named:
5. Troubleshooting
- SMS fires immediately, voice never rings. The voice binding rejected synchronously; the POST response
skipped[]names it with anerror.code(opt-out, unprovisioned sender, bad from-number). The chain advanced in-band before any DLR. - 422
TCPA_FEDERAL_DIALING_WINDOW_BLOCKED. The recipient’s local time was outside the federal 8 AM–9 PM dialing window at dispatch. Schedule per recipient-local time; the window cannot be disabled, and the SMS hop still catches blocked recipients. RECIPIENT_OPTED_OUTon the SMS hop. The recipient opted out between the voice failure and the escalation. Scrub your tenant do-not-call source before the batch (DNC pre-flight scrub).dropped_for_cost_capnames the SMS hop.max_total_pricewas below the cascade’s resolved cost; raise the cap or drop the expensive hop.- Receipt still
in_progresspast the window. A voice receipt that lands afterfallback_window_secondsdoes not escalate; the send is too stale. Size the window to the message’s urgency.
See also
- Voice broadcast with SMS fallback, threaded back into the Inbox — the full walkthrough: compliance gates and the Inbox reply loop.
- Outbound voice broadcast with SMS fallback setup — the dashboard-side campaign plan.
- Channel fallback recipe — the generic two-step and three-step waterfall shapes.
- Track a cascade — field-by-field reading of the hop table.
- Sandbox magic numbers — every deterministic receipt digit.