The sandbox model
Orbit sandbox works because it is a model, not a demo flag in each handler. At one point on the request path the platform stamps the request as test-mode; everything downstream of that stamp — routing, providers, events, billing — reads the stamp and behaves sandbox-safe. This page explains that model: where the boundary sits, how the synthetic provider family answers per channel, how data is isolated, and what carries over when you promote to production. The how-to walkthrough lives in the sandbox and test mode guide; this page is the concept behind it.1. What sandbox is
Sandbox is a synthetic tenant context activated per request, resolved before any routing or provider decision. Three inputs activate it, any one sufficient: adv_test_sk_* API key, an organization flagged as a
sandbox org, or X-Test-Mode: true on a dashboard session. Live
credentials resolve to live; test credentials resolve to sandbox; never
both.
The sandbox context runs the identical API surface with the same
endpoints, request shapes, and webhook payload format. Two things
distinguish it: requests route to a synthetic provider family instead
of carrier dispatch, and event traffic carries distinct markers —
metadata.test_mode: true on messages and events, provider: "sandbox"
on numbers — so a reader or handler can always separate the two worlds.
2. Where the boundary sits
Sandbox mode is resolved on every request by middleware, before any route handler, routing table, or provider adapter runs. The stamp is an input decision evaluated once; downstream layers read it, they do not re-derive it. That ordering buys three properties:- Routing reads the stamp. A sandbox send short-circuits before the routing table picks a carrier. Provider adapters never see it.
- Every response carries the marker. Envelopes say
meta.test_mode: true, so asserting the sandbox boundary in code is a one-field check. - Outbound events carry the marker on every event type. Every event
payload for sandbox traffic carries its
test_modeflag —message.sent, simulated delivery receipts, inbox events — so a handler can gate even if it ignores the separate sandbox webhook URL. A dedicated sandbox webhook URL (under Settings → API Keys → Sandbox) delivers sandbox events to a separate receiver, so a production handler never sees synthetic traffic.
test_sent status and the meta.test_mode: true envelope
field are the two markers that prove the boundary engaged; assert both in
integration tests.
3. The synthetic provider family
Each channel has a sandbox provider that answers without touching a carrier. The response is canned and deterministic so tests stay reproducible:
Every row in the table is persisted with the test marker, and every
emitted event carries it — your webhook handlers and retrieval code run
against real rows, while no carrier ever participates.
4. What you can and cannot do
You can, with no carrier traffic involved: exercise sends on all channels, drive every webhook path with simulated receipts, purchase fictional+1(555)01XX test numbers with provider: "sandbox",
inject simulated inbound messages into a sandbox inbox, and pin
deterministic verification codes (custom_code on the Verify API) for
QA and CI.
You cannot: reach a real carrier, ring a real phone, or hit Meta,
SMPP, or SMTP. Live credentials do not join the sandbox context
403 CUSTOM_CODE_FORBIDDEN on custom_code, and the X-Test-Mode
header is honored only for session auth — a live API key that sends it
stays live.
Sole exception: trial numbers are still real. Because they sit on the
real inventory, a trial number can receive and place real traffic while
the rest of the org is sandbox-only; treat the trial-number boundary as
its own gate.
5. Data isolation
Sandbox rows live in your tenant schema, marked with the test stamp on the message orprovider: "sandbox" on the number. They are never
metered or billed — cost_usd_cents: 0, no wallet debit, no Stripe
traffic — and they are excluded from metrics by filtering on
metadata.test_mode. The tenant-isolation model itself lives in
Tenant isolation.
Purge semantics: POST /sandbox/reset wipes sandbox contacts,
conversations, and messages and releases sandbox numbers, so a test run
can start from a clean slate. The route rejects any non-sandbox request
with 403 SANDBOX_ONLY before a single row changes, so a live context
can never wipe production data through it.
6. Transition to production
At promotion, configuration carries over; traffic does not. Mint a live key, point the same integration code at it, and the following survive: webhook subscriptions and URLs, routing configurations, flows, and reusable templates or lists. The following do not: sandbox message rows, status histories, and purchased sandbox numbers — those stay test-scoped and are wiped by reset. Re-purchase live numbers and re-register a live sender against the live credential; the pre-launch checklist reports readiness while any of those steps is pending. Carries over vs. does not:7. Pointers
- Sandbox and test mode guide — the end-to-end walkthrough (magic numbers, test numbers, simulated inbound, deterministic verify codes, go-live gate). Pin markers as in provision and test mode.
- Sandbox overview — the orientation page: the full endpoint surface and billing posture.
- Sandbox magic numbers — the digit-to-scenario table for simulated receipts.
- Pre-launch checklist — the five-step go-live gate.
- Provisioning and test mode concept — the three test-mode switches and what a test send does and does not touch.
- Tenant isolation — the per-tenant schema sandbox rows live in.