Skip to main content
Didit’s sandbox is the equivalent of Stripe’s test cards or Sumsub’s document templates: it lets you reproduce any verification outcome deterministically, without touching real providers, real PII, or your balance.
The source of truth for this data is serialize_catalog() in the verification service, served live at GET /v1/sandbox/scenarios/. A drift-tested copy is generated into docs/sandbox/SANDBOX_REFERENCE.md in the service-didit-verification repo via pipenv run python scripts/generate_sandbox_reference.py. This page mirrors that reference — always trust the live catalog endpoint if anything here looks stale. The catalog endpoint is authenticated: send your API key (x-api-key) the same way you would on any other call, or a session token if you are reading it from inside a running session.

Environment model

Sandbox is a per-application mode (ApplicationDetail.mode == SANDBOX), the same idea as Stripe test mode. While a session runs under a sandbox application:
  • Every external provider (OCR, AML, face match, liveness, NFC, phone, email, IP, POA, database validation, document AI) is mocked — no third party is ever called.
  • Sessions are never billed and the balance check is bypassed.
  • A per-application quota of 500 session creates / 24h is enforced.
  • Webhooks carry "environment": "sandbox" (live sessions carry "environment": "live").
  • The hosted flow exposes the session environment so the UI can warn users not to upload real PII.
  • Outcomes never depend on the pixels: extracted data and statuses come from the scenario / magic value. The captured media itself (documents, selfie, video, POA files) is stored for real, exactly like a live session.

Transaction Monitoring in sandbox

Sandbox is session-shaped: it mocks the providers a verification session would call. Transaction Monitoring has no provider to mock, so a sandbox application takes the other route: the transaction is stored, but nothing that decides, screens, notifies or charges ever runs over it. A transaction created on a sandbox application is persisted and returned as usual, and none of the following happens to it:
  • No rule evaluation. rule_runs is empty, whatever mode your rules are in. An ACTIVE rule on a sandbox application never fires, so the transaction stays APPROVED with score 0 and no alert or case is opened.
  • No AML / wallet screening. provider_results is empty even for crypto transactions on an application with monitoring enabled.
  • No webhooks. Neither transaction.created nor transaction.status.updated is delivered for a sandbox transaction.
  • No billing. A usage event is recorded at price 0.0000 and your balance is untouched.
Every transaction response carries an environment field ("live" or "sandbox") so an empty rule_runs is never ambiguous: "sandbox" means the rules were not evaluated, not that none of them matched. The Business Console says the same thing in the transaction’s Risk & decision card instead of a bare “No rules evaluated”.
Travel Rule is the one exception to “nothing runs”. On an application with Travel Rule enabled, a sandbox travelRule transaction still runs the exchange engine: the transfer record is created and moves through its statuses. What sandbox never does is talk to anyone - no compliance message reaches a counterparty VASP over any rail (platform network memberships, BYOK, email, or INTERNAL), counterparty VASP attribution screening is skipped, cancellations are a no-op, and travel_rule.status.updated webhooks are not delivered. The two directions then behave differently, because only one of them dispatches:Outbound - a travelRule transaction you submit. The threshold_amount and self-hosted-wallet checks apply as normal, then dispatch is simulated: no rail is selected, so the transfer parks at AWAITING_COUNTERPARTY, exactly like a real send whose rail callbacks have not arrived yet. Nothing advances it from there, so a full two-sided rail loop can only be exercised between live applications.Inbound - POST /v3/travel-rule/inbound/. Registration never dispatches over a rail, so nothing about it is simulated: sandbox resolves the deposit against your wallet address book and settles synchronously, exactly as live does. It resolves to a real outcome rather than AWAITING_COUNTERPARTY - ON_HOLD (no address book match), UNCONFIRMED_OWNERSHIP (matched entry is not ownership-verified), COUNTERPARTY_MISMATCHED_DATA (beneficiary name does not match the wallet holder), or COMPLETED then FINISHED (matched a verified entry). This makes inbound registration the one Travel Rule flow you can exercise end to end in sandbox.
How to exercise a rule before it goes live. Use Backtest: it replays the rule against your real transaction history and reports evaluated / matched / affected_entities without touching any transaction’s status. Pair it with TEST mode on the live application - a TEST rule evaluates on every new live transaction and records what it would have matched, without changing the score or status - and switch to ACTIVE once the selectivity looks right.

How to switch between live and sandbox

Live and sandbox are separate applications, so test traffic and production data never mix. Each application has a mode of live or sandbox:
  • Use a sandbox-mode application’s API key to get mocked, unbilled, simulatable sessions.
  • Use a live-mode application’s API key for real verifications.
The sandbox_scenario field (on session create and on the standalone AML API) and the simulate endpoints are only accepted on sandbox applications; they are rejected on live applications.

Sandbox in the hosted verification flow

Sessions created under a sandbox application render extra UI in the hosted verification flow (the url returned by POST /v3/session/):
  • In-card sandbox banner. Every screen shows a banner inside the card - “Test data only - no real calls” - so it is always obvious the session never calls real providers and never touches your balance. Captured media is stored like any session, so use the sample documents and test data rather than real identity documents or personal information.
  • Pre-flow scenario picker. Before capture starts, an in-card picker walks the user through choosing a test result: pick an outcome, then a trigger for that outcome. Confirming calls POST /v3/session/{session_id}/sandbox/arm/ (see Arm (or re-arm) the session before the flow starts below), then continues into the flow with that scenario’s magic values already applied.
  • Sample documents strip. A single row of sandbox sample documents (up to 3) sits directly below the document upload/capture component. Tap one to use it instead of a real upload.
Sandbox sessions store the real captured media - documents, selfie, liveness video, and proof-of-address files are uploaded and retained exactly as they would be for a live session. The extracted data (ID verification fields, face match scores, liveness results, and similar) and the session outcome itself (Approved / In Review / Declined) are simulated by the scenario or magic value - never derived from the captured media. The Business Console marks the extracted-data sections of a sandbox session with a Simulated data chip, so reviewers can tell at a glance which parts of the session are the real capture and which are scenario-driven.

Scenarios

Each scenario is a named bundle of magic values. Pass its slug as sandbox_scenario on session create (or to the simulate endpoint) to reproduce the outcome. review_* scenarios exist so you can test manual-review UX (the console review queue, In Review webhooks, reviewer approve/decline actions) without needing a decline-grade input. The two kyb_* scenarios above set expected_details.registration_number instead of expected_details.first_name: they only apply to Business Verification (KYB) sessions.

Magic values

A magic value is a specific input that makes one mocked feature emit a specific risk. Set the input field to the magic value (directly, or via a scenario) to trigger it.

Non-doc lookup and identity wallets

Sandbox applications can enable every identity wallet in the country catalog, including wallets that are unavailable on live applications. The default approve scenario simulates a verified identity without signing in to an external wallet. The result is stored in the session and follows the workflow’s normal next step. Sandbox does not validate a real wallet credential or charge for simulated attempts. Lookup requests still validate the entered fields and use the configured attempt limit, which defaults to one. Use the following scenarios to exercise retries and fallback rules: Wallet failures follow the country setting to fall back to document capture or decline. Completed lookup attempts are recorded separately, so two allowed attempts produce two results before fallback. All sandbox usage remains free.

The sample documents

Never upload real identity documents in sandbox. Use the built-in sample documents instead.
The hosted flow offers three sample documents, one per document type: Tap a sample in the strip to use it instead of a real capture. The extracted data and the session outcome always come from the selected scenario or magic value, so any sample works with any scenario.

Sample identity: testing expected_details

Every sandbox document is fabricated from one pinned identity. The extracted values are the same on every session and are never derived from the current date, so you can prefill expected_details with them and check your field-comparison and warning handling end to end. Pass the values below and the comparison checks pass; pass anything else and the matching *_MISMATCH_WITH_PROVIDED warning fires on purpose. Dates are shared by every sample document: The rest of the identity depends on which document you use:
first_name and last_name are the exception: the sandbox echoes the names you send in expected_details onto the fabricated document, so name matching passes with any name. Use the SANDBOX_OCR_FULL_NAME_MISMATCH_WITH_PROVIDED magic value to exercise a name mismatch, and SANDBOX_OCR_DOB_MISMATCH_WITH_PROVIDED for a date-of-birth mismatch.
If your workflow restricts documents_allowed to a country or document type that excludes the Brazil ePassport, the sandbox fabricates a document from the first allowed country instead, so nationality, id_country, and the document name follow that country. The dates above stay the same.

What the fabricated document does not carry

The fabricated document is the generic template for the allowed country and document type, so the fields that only a real, state-issued document provides stay empty. The one that matters for routing is the region / US state (kyc.region): a sandbox US driver’s license or ID card has no state, so a branch condition such as Region equals IL can never match in sandbox and, when no other branch of that node matches either, the flow takes the node’s fallback branch (the branch with no conditions). This is not a review or a failure of the branch - the condition simply has nothing to read. A missing field also fails not equals and not in; see how branch conditions read session data for the difference between a missing field and an empty string. To exercise a state-dependent flow in sandbox, drive the decision from a value you control on the session rather than from the document:
  • Send the state in metadata when you create the session (for example "metadata": {"credential_state": "IL"}) and branch on metadata.credential_state. Metadata is evaluated in sandbox exactly as it is live.
  • Keep the document-derived kyc.region condition for live traffic if you also need the document to prove the state; in live mode the region is read from the real document.
Everything else about routing is identical to live: the workflow graph, branch conditions, metadata and expected_details values, action nodes and status nodes all run for real. Sandbox never forces a session into review on its own - a sandbox session ends In Review only when the workflow routes it there (a review_* scenario, a magic value mapped to a review action, or a status node in the graph).

OTP codes and email opt-in

  • Phone OTP: the sandbox phone check accepts any well-formed code (4-8 digits; 123456 included). Malformed input still returns 400.
  • Email OTP: the sandbox email check accepts any well-formed code (123456 included). Malformed input still returns 400.
By default sandbox sessions do not send emails (review alerts, status change, OTP, resubmit, new-session-link, etc.). To deliver an email anyway, use one of these opt-in patterns on the recipient address:

Driving outcomes: scenarios, magic values, arming, and the simulate endpoint

There are five ways to control a sandbox outcome.

1. sandbox_scenario at session create

Set the scenario slug when creating the session (POST /v3/session/). The create flow expands the scenario’s bundled magic values into the session’s input fields, so the mocked providers emit the intended outcome through the real workflow pipeline.

2. Magic values typed by hand

Set the individual input fields yourself (for example expected_details.first_name = "SANDBOX_OCR_DOCUMENT_EXPIRED", or user_email = "sandbox+aml_hit@didit.test"). See the magic values table above for the full registry.

3. Arm (or re-arm) the session before the flow starts

Set or replace the armed scenario on a session that already exists, as long as it hasn’t progressed past Not Started. This is the endpoint the pre-flow scenario picker calls, and you can call it yourself to build a custom picker or test harness:
200 response:
  • Authenticate with the session’s own Session-Token (returned as session_token from session create), not your application API key: this is a client-facing endpoint.
  • Returns 400 for an unknown scenario slug, and 400 once the session has moved past Not Started ("Scenario can only be armed before the verification starts.").
  • Returns 404 when the Session-Token belongs to a live (non-sandbox) application, or doesn’t match the session_id in the path.
  • Re-arming replaces the previous scenario. It clears only the magic values the previous scenario wrote, and leaves anything the user already typed by hand (email, first name, etc.) untouched. For example: arm decline_phone_high_risk, then arm approve - the phone number reverts to unset, but a customer-entered email survives both calls.

4. The simulate endpoint

Force a terminal status after the fact:
  • Console / API key (requires the write:sessions privilege): POST /v3/session/{session_id}/simulate/
  • Hosted flow (Session-Token header): POST /v3/session/{session_id}/sandbox/simulate/
Body fields: new_status, scenario, risk, comment, node_id — all optional, but at least one of new_status, scenario, or risk is required. When only scenario or risk is given, new_status defaults to that scenario’s / risk’s default status, and the matching warning Log is written.

5. sandbox_scenario on the standalone AML API

Back-end AML screening has no end user and no session to arm, so the slug goes on the screening request itself (POST /v3/aml/):
  • decline_aml_hit returns one production-shaped hit and settles the screening as Declined; review_aml_possible_match settles it In Review. A scenario that arms no AML outcome leaves the screening clean.
  • With save_api_request: true (the default) the screening is persisted exactly as it is on a live key, so the returned request_id resolves through GET /v3/session/{request_id}/decision/ and GET /v3/session/{request_id}/generate-pdf/.
  • Returns 400 for an unknown scenario slug, and 400 when the API key belongs to a live application.

Where sandbox_scenario appears

The armed scenario slug (null if none is armed) is echoed back on some surfaces and intentionally left off others: If your integration needs to branch on the armed scenario server-side, read it from the webhook envelope or either decision endpoint (v1 or v3).

Usage and billing

Sandbox usage is tracked, never charged:
  • Every mocked call still records a usage event (is_sandbox: true, priced at 0.0000) so sandbox volume is visible for debugging, but it is never added to your usage totals, never decremented from balance, and never appears on an invoice.
  • The Business Console’s balance, top-up, and live-usage surfaces filter sandbox usage out by default. The top-up usage endpoints accept an environment parameter (live, the default, or sandbox) so sandbox volume can be inspected separately - always at zero cost and with no free-tier consumption.
  • Email and phone verification bill per request rather than per session outcome, and sandbox has no way to fully mirror that at the webhook layer. Sandbox email/phone checks still record a usage event per request at price 0.0000, same as every other mocked feature: this is an accepted, known divergence from live billing behavior, not a bug.
  • Database validation’s response shape in sandbox intentionally diverges from the live per-provider shape, since there is no real provider to mirror. Treat sandbox database-validation payloads as a stand-in for the outcome (status and warning code), not a byte-for-byte preview of a live provider response.