Concepts
Test, sandbox & live mode
Every Matter account ships with three universes — test, sandbox, and live. Same endpoints, same SDK, same dashboard. Different data, different filings, different signing keys. Use test to dry-run with deterministic simulators, sandbox for production-grade staging with synthetic providers, and live when you're ready for real Secretary of State and IRS traffic.
Last updated
Matter gives every account three completely separate universes — test, sandbox, and live. Same endpoints. Same SDK. Same dashboard. Different data, different filings, different webhook signing secrets.
testis a deterministic developer sandbox: free, controllable test clock, simulators for every external provider. Drop into your CI or unit tests; reset whenever you want.sandboxis the production-grade staging environment: live business rules, distinct signing keys, isolated data — but every external provider is synthetic. No real IRS, no real Secretary of State, no real money. Use it for customer-staging integrations and dry-running real flows before flipping to live.liveis real: real filings, real EINs, real money, real webhooks to real endpoints.
You land in test mode on day one. Flipping to sandbox or live is an explicit
decision — via the three-segment Test / Sandbox / Live switch in the
dashboard chrome, or by swapping sk_test_ for sk_sandbox_ / sk_live_
in your SDK key.
The mental model
| Test | Sandbox | Live | |
|---|---|---|---|
| Default for new accounts | ✓ | ||
| API key prefix | sk_test_ / pk_test_ | sk_sandbox_ / pk_sandbox_ | sk_live_ / pk_live_ |
mode on every Prisma row | "test" | "sandbox" | "live" |
| Filings hit the SoS | Simulated (deterministic) | Simulated (production-grade) | Real |
| EINs hit the IRS | Simulated | Simulated | Real |
| ACH / wire moves money | Simulated | Simulated | Real |
| Webhooks fire | ✓ (whsec_test_) | ✓ (whsec_sandbox_) | ✓ (whsec_live_) |
| Audit chain | Forked on (orgId, mode) | Forked on (orgId, mode) | Forked on (orgId, mode) |
| Test-speed knob honoured | ✓ | partial | no |
| Free | ✓ | ✓ | charged per real action |
The three universes never bleed into each other. A sk_test_ bearer cannot
read sandbox or live rows; a sk_sandbox_ bearer cannot read test or live;
a sk_live_ bearer cannot read test or sandbox. The audit chain is
cryptographically forked at the storage layer (AuditEntry carries
@@unique(orgId, mode, prevHash)), so a test-mode hash chain physically
cannot be extended by a sandbox or live bearer.
How mode flows through the system
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Authorization: │ │ Token row in DB │ │ EntityServiceAuth │
│ Bearer sk_test_… │───▶│ Token.kind=sk_test │───▶│ mode="test" │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
│
▼
┌─────────────────────┐ ┌─────────────────────┐ ┌─────────────────────┐
│ Entity row written │◀───│ createEntity() │◀───│ Prisma WHERE mode │
│ mode="test" │ │ writes from auth │ │ = auth.mode │
└─────────────────────┘ └─────────────────────┘ └─────────────────────┘
│
▼
┌─────────────────────┐
│ AuditEntry │
│ (orgId,mode, │
│ prevHash) unique │
└─────────────────────┘There is no X-Matter-Mode header. There is no mode request body field.
The token is the mode. That single invariant is what makes the isolation
tamper-proof: every Prisma write derives mode from auth.mode, which
derives from Token.kind, which was assigned at key-mint time and cannot
be forged at request time.
Switching modes in the dashboard
The top-right of the dashboard chrome has a three-segment switch:
┌────────────────────────────┐
│ Test │ Sandbox │ Live │ ← active segment is highlighted
└────────────────────────────┘Test sessions show a clay-red Test pill in the bottom-left; sandbox
sessions show a distinct indigo Sandbox pill; live is banner-less. The
mirrored 3-segment toggle on Settings → Developers (/settings/admin)
shows the same state with tone-coded affordances.
Clicking the inactive segment:
- Rotates your dashboard session token — the old token is revoked
with reason
mode_rotation:to_<mode>; a freshsk_dashboard_{test|sandbox|live}_…token is minted. - Swaps the
matter_session_token+matter_modecookies. - On first entry to test mode for your account, seeds five demo entities (Waypoint, Lattice, Orbital, Ferment, Coral) at varied lifecycle stages so the dashboard has interesting data to explore. Sandbox and live start empty — you populate them via real flows.
- Persists the preference on your Clerk user
(
publicMetadata.matterMode) so the choice survives sign-out / sign-in. - Revalidates the layout so the banner, the OrgSwitcher, and every mode-scoped query re-renders in the new mode.
Switching modes from the SDK
Switching modes in code is a key prefix swap. The SDK reads the prefix from the key and routes mode automatically.
import Matter from "@mattermode/sdk";
// Test mode — deterministic simulators, free, controllable clock.
const test = new Matter({ apiKey: process.env.MATTER_KEY_TEST });
// Sandbox — production-grade rules, synthetic providers.
const sandbox = new Matter({ apiKey: process.env.MATTER_KEY_SANDBOX });
// Live — real filings, real money, real state-of-incorporation traffic.
const live = new Matter({ apiKey: process.env.MATTER_KEY_LIVE });
// Different keys, same client, same shape — different data.
await test.entities.create({ name: "Acme, Inc.", jurisdiction: "DE", entityType: "c_corp" });
await sandbox.entities.create({ name: "Acme, Inc.", jurisdiction: "DE", entityType: "c_corp" });
await live.entities.create({ name: "Acme, Inc.", jurisdiction: "DE", entityType: "c_corp" });All three keys are minted on first sign-in and surface at
/settings/api-keys with a rotate-and-reveal
affordance per kind.
Speed knobs in test mode
Real Delaware filings take 1–3 business days. The IRS issues EINs in roughly the same window. ACH transfers settle T+1 to T+3. Test mode gives you a knob to compress or skip these waits.
Pass X-Matter-Test-Speed: instant | fast | real on any test-mode write:
| Speed | Behaviour | When to use |
|---|---|---|
instant (default) | Pipeline runs inline; response returns the terminal state | CI, demos, fast iteration |
fast | Pipeline runs inline with a 1-second setTimeout delay | Local dev when you want to see "pending → completed" UX |
real | Enqueues a BackgroundJob with a delay calibrated to real-world turnaround | Stress-testing webhook handlers against a realistic async cadence |
Live-mode bearers passing X-Matter-Test-Speed get 400 invalid_header_for_live_mode.
Sandbox-mode bearers may accept the knob on some endpoints; the header
is intended primarily for test use.
# Watch the dashboard transition through pending → registered over ~30 min.
curl https://api.mattermode.com/v1/entities/ent_…/submit \
-H "Authorization: Bearer $MATTER_KEY_TEST" \
-H "X-Matter-Test-Speed: real" \
-H "Idempotency-Key: $(uuidgen)"Webhooks per mode
Webhook endpoints, like everything else, are mode-scoped. Configure them
separately at /settings/webhooks:
- Test endpoint — receives test events. Signs with your test webhook
secret (
whsec_test_…). - Sandbox endpoint — receives sandbox events. Signs with your
sandbox webhook secret (
whsec_sandbox_…). - Live endpoint — receives live events. Signs with your live webhook
secret (
whsec_live_…).
Each mode's webhook signatures are produced from a distinct key hierarchy — they cannot be laundered across modes. Don't share a single endpoint between modes; use the dedicated URLs.
When to use each mode
You're in test mode by default. Choose the right mode for the work:
- Test — local dev, unit tests, CI. Free, deterministic, controllable test clock. Cheap to throw away and re-create.
- Sandbox — staging environments, integration tests with real customer-facing handlers, dry-running a real flow before you flip to live. Production-grade behaviour, isolated data, no real-world side effects. Reset on cadence.
- Live — actually forming a company, actually filing, actually moving money.
The work you do in test or sandbox does not "promote" to live. Each universe stays separate; flipping the toggle hides the others but doesn't migrate them. The right pattern is to build / iterate / verify in test, validate against production-grade rules in sandbox, then redo the equivalent flows for the real entity in live.
What's different in each mode
| Surface | Test | Sandbox | Live |
|---|---|---|---|
Entity.externalFileNumber | DE-DEMO-A1B2C3D4 | DE-SBX-A1B2C3D4 | Real DE file number |
Entity.externalEin | 99-DEMO1234567 | 99-SBX1234567 | Real IRS EIN |
| Filing pipeline | DemoFilingProvider | SandboxFilingProvider (planned) | Real SoS integration |
| 409A valuations | Stub appraiser stamps | Stub appraiser stamps | Real appraiser PDFs |
Document.executedAt | Set immediately under testSpeed: "instant" | Set when synthetic signer acks | Set when real counterparty signs |
| Resolutions | Auto-signed under testSpeed: "instant" | Signed via the sandbox director simulator | Real director / stockholder signatures |
| Banking transfers | Settled in the simulator | Settled in the synthetic ledger | Real ACH / wire / book |
| Webhook signing | whsec_test_… hierarchy | whsec_sandbox_… hierarchy | whsec_live_… hierarchy |
Common gotchas
Modes don't bleed. A row written under one mode is invisible to the other two. If your dashboard looks empty after switching modes, that's expected — populate that universe from the SDK or the dashboard while pinned to it.
The same bearer can't read multiple modes. If you await client.entities.list() from a sk_test_ key, you get test entities.
A sk_sandbox_ key returns sandbox entities. A sk_live_ key returns
live. There is no single bearer that reads more than one mode.
Webhook signatures don't validate cross-mode. Test signatures never validate against your sandbox or live secret, and so on. If you're seeing signature-verification failures after a webhook deploy, confirm you've configured the right secret for the endpoint's mode.
Idempotency-Key is token-scoped. Replaying the same idempotency
key with a different mode's bearer is a fresh request, not a replay.
That's by design — idempotency keys are part of the mode isolation,
not orthogonal to it.
See also
- API keys — minting and rotating
sk_test_/sk_sandbox_/sk_live_triplets. - Webhooks — per-mode endpoint configuration.
- Idempotency — replay semantics.
Matter-Versionheader — date-pinning your integration against the API contract.