Sandbox & Test Data
Sandbox is a full copy of the verification pipeline with its own credentials and its own data. Use it to build and prove your integration before any real customer reaches it.
Access
| What you need | How you get it |
|---|---|
| Sandbox base URL | Issued by Inyo at onboarding |
client_id / client_secret | Issued by Inyo at onboarding, separate from production |
webhook_secret | Issued by Inyo at onboarding, separate from production |
| Network access | Your egress IP ranges must be allowlisted β send them to your Inyo contact |
A registered webhook_url | Optional, but needed to exercise webhook delivery. A tunnel works during development |
Sandbox and production credentials are never interchangeable, and a sandbox verification is never a valid basis for a real onboarding decision.
Sandbox Runs Real Verification
Sandbox performs real document analysis β the same extraction, authenticity, liveness, and face-match pipeline as production. There is no fixed set of test document numbers that produce canned outcomes, the way test card numbers work for payments.
The practical consequence: test with real documents you personally control, or with documents issued for testing. A verification will genuinely fail if the image is blurry, the document is expired, or the selfie does not match β which is exactly what makes sandbox useful for proving your error handling.
Every result carries a
providerfield."inyo"means real analysis produced it."inyo-mock"means a simulator did β which never happens in production, and tells you unambiguously that a result did not come from real verification.
Driving Each Outcome
| Target | How to produce it |
|---|---|
approved | A valid, unexpired document you control, well lit and in frame, with a selfie of the same person |
declined β expired document | An expired document. document_not_expired fails |
declined β face mismatch | A document belonging to one person with a selfie of another. face_match fails |
declined β screen recapture | Photograph the document off a phone or monitor screen. document_authentic and/or screen_pattern fail |
declined β unreadable | A deliberately blurry or partially covered capture. document_readable fails |
declined β capture attempts | Fail capture repeatedly in the widget until the limit is reached, adding a capture_attempts check |
in_review β soft check | Send data_check: true with a prefill name or date of birth that does not match the document. data_match fails |
in_review β held rejection | Ask Inyo to enable held rejections on your sandbox tenant, then produce any readable-document decline |
in_review β borderline pass | Ask Inyo to set a review threshold on your sandbox tenant above your face-match threshold, then verify with a marginal selfie |
expired | Create a session and leave it unused past the link's 48-hour lifetime |
422 responses | Request a document type you do not have enabled, or send a malformed prefill.document_number |
502 | Not reproducible on demand β handle it as a transient retry path in code |
Ask for a separate sandbox tenant if you need conflicting configurations. Review routing and held rejections are tenant-level settings, so exercising both a normal decline and a held decline means changing configuration between runs β or having two sandbox tenants.
Testing Without a Camera
The document-number format validator needs no credentials and no images, which makes it the fastest thing to integrate against first:
curl --request POST \
--url https://{FQDN}/v1/validators/document-number \
--header 'Content-Type: application/json' \
--data '{"document_type": "drivers_license", "number": "not-a-number", "issuing_state": "PA"}'
{
"document_type": "drivers_license",
"number": "not-a-number",
"issuing_state": "PA",
"valid": false,
"rule": "us_dl:PA",
"detail": "document number does not match the PA license format"
}
Use it to confirm your handling of all three states β true, false, and null for an unknown jurisdiction. See Verification Sessions.
Testing Result Delivery
Delivery is worth testing on its own, independently of verification outcomes:
- Signature verification β capture one real sandbox webhook body and its
X-Inyo-Signature, then unit-test your verifier against those exact bytes. Add a case that mutates one byte of the body and asserts rejection, and a case that re-serializes the parsed JSON and asserts rejection. Those two cases catch the mistake that breaks most integrations. - Retry behavior β return
500from your endpoint and confirm you see up to three attempts, then nothing. - Ordering β replay two stored notifications for one session out of order and assert your handler keeps the one with the higher
notified_at. - Idempotency β deliver the same result twice and assert your system does not double-process it.
Steps 1, 3, and 4 need no sandbox call at all once you have captured one real payload.
Before You Go Live
| Check | Why |
|---|---|
| Signature verification runs against raw bytes | The most common production failure |
in_review has a pending state in your model | It is neither approved nor rejected, and it arrives on redirects too |
Concurrent results resolve by notified_at | Deliveries can arrive out of order |
| A completed outcome can be reversed | Quality-control overrides re-deliver a changed result |
502 retries rather than declining | It is an infrastructure failure, not a customer outcome |
| Production credentials are separate and the base URL is switched | Sandbox tokens are not valid in production |
| A reconciliation job polls non-terminal sessions | Delivery is best-effort; GET /v1/sessions/{session_id} is authoritative |
Next Steps
- Getting Started β the end-to-end walkthrough
- Receiving Results β signatures, retries, and ordering
- Tenant Configuration β the settings to request on your sandbox tenant
