Shipping an SMS or voice integration untested against delivery behavior is how launch day turns into a rollback. The failure modes that hurt — a webhook handler that drops the terminal receipt, a retry loop that storms after an undelivered, a go-live blocked on an unminted test key — are all things you can exercise before a single real carrier sees your traffic. That is what a sandbox is for, and this post walks Orbit's: the magic-number simulator, the five-step pre-launch gate, a worked OTP test, and the checklist for promoting to production.
Why a sandbox matters for SMS, voice, and WhatsApp
Messaging and voice integrations fail in production for reasons that have nothing to do with your code's happy path. Carriers return ten different terminal states, not two. Delivery receipts arrive out of order, late, or never. WhatsApp sessions expire. Spam filters reject, suppression lists block, and sender IDs get rewritten in transit.
If the first time your webhook handler sees an expired receipt is in production, you are debugging against live customer traffic. If your retry logic only ever saw delivered in testing, it may hammer a revoked destination until your frequency caps trip. A sandbox that can simulate every terminal state — on demand, deterministically, with no billing — moves that debugging to where it belongs: before launch.
The cost asymmetry is the point. A missed DLR edge case found in sandbox costs minutes. The same edge case found in production costs a support incident, a rollback, or a carrier relationship.
What Orbit's sandbox gives you
Every Orbit account ships with a paired sandbox workspace. Sandbox mode activates on any of three signals: a sandbox API key (prefix dv_test_sk_*), a sandbox-flagged workspace, or the X-Test-Mode: true header from a dashboard session. Every sandbox mutation endpoint rejects a live context with 403 SANDBOX_ONLY, and sandbox traffic never bills — every send reports cost_usd_cents: 0 and never touches your wallet.
The dashboard's Developer → Sandbox page wraps the same operations the API exposes, so you can work from the console or from code:
- Magic numbers. The recipient's trailing digit picks the simulated delivery outcome — delivered, failed before submit, expired, unknown, rejected by a spam filter, blocked by suppression, delayed, or a carrier rewrite. Each scenario emits the same intermediate-then-terminal receipt sequence a real carrier produces, with realistic latency (from ~50 ms to ~60 s), so your webhook handler exercises the full status lifecycle, not just the happy ending.
- Provisioned test numbers. Mint deterministic fictional
+1(555)01XXnumbers to use as senders in test sends. - Fixture contacts. Seed deterministic contacts — optionally with conversations — so your inbox and campaign flows have something to work on.
- Inbound injection (Virtual Phone). Simulate an inbound SMS, MMS, or WhatsApp message into your inbox, so you can test two-way flows without a real handset.
- Reset. Wipe contacts, conversations, messages, and release sandbox numbers, so test runs start clean.
- Pre-launch checklist. A five-step go-live gate, readable from the dashboard and from
GET /sandbox/pre-launch-checklist, that reportsreadyForLaunch: trueonly when every step is complete.
One honest limit: sandbox voice calls currently resolve completed regardless of the recipient's trailing digit. The no-answer and busy outcomes exist in the simulator as a planned mapping; the messaging simulator is the fully wired one today.
Worked example: testing an OTP flow end to end
Say you are shipping one-time-password SMS for sign-in. Before you spend on TCR registration and live 10DLC throughput, you want to prove the whole loop: the send, the receipt handling, the retry, and the timeout path.
With a sandbox key, send to a magic recipient whose trailing digit selects the outcome your OTP verifier must handle:
# Happy path — trailing 2 → sent, then delivered (~50 ms)
curl -X POST https://api.orbit.devotel.io/api/v1/messages/sms \
-H "X-API-Key: $ORBIT_SANDBOX_KEY" \
-H "Content-Type: application/json" \
-d '{
"to": "+15005550002",
"from": "+15005550101",
"body": "Your code is 482913"
}'Your sandbox webhook URL receives a message.status event per state transition — an intermediate sent, then the terminal outcome. Each receipt carries state_class and is_terminal, so your handler can tell an in-flight update from a final answer.
Then exercise every branch your OTP logic must survive:
- Trailing `3` (
undelivered, ~1.15 s): your retry policy should back off and re-send — verify it does not loop forever. - Trailing `5` (
expired, ~3 s): the TTL-elapsed path — verify your verifier treats this as "resend," not "block the user." - Trailing `1` (submitted only, no terminal receipt): your DLR-timeout handling — the state your code must not mistake for delivered.
- Trailing `7` (
rejected, ~150 ms): the path where retrying is wrong — re-sending to a filtered destination just burns quota.
Configure the sandbox webhook URL separately from your live one under Settings → API Keys → Sandbox, so production handlers never see test traffic. When a run is done, call POST /sandbox/reset and the next run starts clean.
Promoting from sandbox to production
The pre-launch checklist is the shared gate. It evaluates five ordered steps against your live workspace and reports readyForLaunch: true only when all five complete:
- Sandbox workspace ready — the paired sandbox exists and is provisioned.
- Live workspace ready — your live workspace exists and is not itself a test workspace.
- Test key minted — at least one sandbox key is active on the live workspace.
- Integration exercised — a sandbox key has actually authenticated a request, so the code ran for real.
- IP allowlist configured — at least one live key carries an allowlist, so production credentials do not ship wide open.
Wire GET /sandbox/pre-launch-checklist into your launch script and fail CI on an unfinished gate. Beyond the gate, walk the human checklist: swap dv_test_sk_* for dv_live_sk_*, repoint webhooks from the sandbox URL to the live URL, register and provision your production numbers (10DLC/TCR for US SMS), and rate-limit your go-live ramp.
How this compares to Twilio and Plivo test credentials
| Twilio test credentials | Plivo sandbox | Orbit sandbox | |
|---|---|---|---|
| Scope | SMS inspector-style test numbers; limited simulation | Magic numbers for SMS | SMS, MMS, WhatsApp, RCS, Viber, Telegram, Messenger, AMB, email; voice |
| Delivery states | A handful of fixed magic-number outcomes | Limited canned responses | Ten deterministic terminal states with realistic receipt latency and intermediate/terminal tagging |
| Inbound simulation | Not first-class | Not first-class | Virtual Phone injects inbound SMS/MMS/WhatsApp into your inbox |
| Go-live gating | Manual | Manual | Five-step checklist endpoint that reports readyForLaunch, scriptable from CI |
| Workspaces | Test credentials on one project | One account mode | Paired sandbox workspace with dashboard toggle and 403 SANDBOX_ONLY guard |
The difference is the shape of the thing: Orbit's sandbox is a full workspace with a launch gate, not a couple of magic numbers hanging off your production project.
Where to start
Open Developer → Sandbox in the dashboard, or go straight to the docs: the sandbox overview orients you, the magic numbers reference lists every simulated outcome, and the pre-launch checklist is the go-live gate. New to Orbit? The quickstart gets you a sandbox key in a few minutes.