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 |
webhookSecret | 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 webhookUrl | 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. No test document number produces a canned verification outcome the way test card numbers work for payments: approval, decline, and review all come from the images you submit.
The one exception is the document-number format check, which does accept reserved values β see Reserved Test Document Numbers. It checks the shape of a declared number and never looks at an image, so it is a separate surface from the verification pipeline described here.
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_scene_clear fails |
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 dataCheck: true with a prefill name or date of birth that does not match the document. 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.documentNumber |
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 images and consumes no verification, which makes it the fastest thing to integrate against first:
curl --request POST \
--url https://{FQDN}/v1/validators/document-number \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"documentType": "drivers_license", "number": "not-a-number", "issuingCountry": "USA", "issuingState": "PA"}'
{
"documentType": "drivers_license",
"issuingCountry": "USA",
"issuingState": "PA",
"valid": false,
"rule": "dl:USA: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 Document Number Validator.
Reserved Test Document Numbers
The document-number format check accepts a small set of reserved values that return a chosen verdict on demand. Reach for them when you need a specific valid outcome β most often from remittance sender creation, which runs this check on every declared document β without first learning a jurisdiction's real number format.
Sandbox only. These values are enabled per environment; if the ones below return an ordinary verdict, ask your Inyo contact to enable them on your sandbox tenant.
Every capturable type publishes all three valid states, so you can exercise your own handling per type without learning a jurisdiction's real number format. ssn and itin carry a passing value only β see below.
documentType | issuingCountry | issuingState | number | valid |
|---|---|---|---|---|
drivers_license | USA | PA | 99900001 | true |
drivers_license | USA | PA | 99900002 | false |
drivers_license | USA | PA | 99900003 | null |
drivers_license | BRA | β | 99900000070 | true |
drivers_license | BRA | β | 99900000188 | false |
drivers_license | BRA | β | 99900000296 | null |
passport | USA | β | 999000001 | true |
passport | USA | β | 999000002 | false |
passport | USA | β | 999000003 | null |
identity_card | MEX | β | XXXX000101HXXXXX01 | true |
identity_card | MEX | β | XXXX000101HXXXXX02 | false |
identity_card | MEX | β | XXXX000101HXXXXX03 | null |
ssn | USA | β | 078051120 | true |
itin | USA | β | 912891234 | true |
The identity_card rows are Mexican because there is no US identity-card rule to test against β a US value would be a test case for a check that does not exist. A CURP encodes the holder's name and birth date, so the all-X name field is structurally valid and cannot collide with a real person.
Send each number with the issuingCountry and issuingState shown. A reserved value is matched on that exact pairing, so the same number under any other jurisdiction is validated by the ordinary rules.
curl --request POST \
--url https://{FQDN}/v1/validators/document-number \
--header "Authorization: Bearer $ACCESS_TOKEN" \
--header 'Content-Type: application/json' \
--data '{"documentType": "drivers_license", "number": "99900002", "issuingCountry": "USA", "issuingState": "PA"}'
{
"documentType": "drivers_license",
"issuingCountry": "USA",
"issuingState": "PA",
"valid": false,
"rule": "reserved:drivers_license:PA:invalid",
"detail": "reserved sandbox test value β simulated invalid verdict"
}
A simulated result carries the response header X-Inyo-Simulated: true. It is present only when a reserved value produced the result, and absent otherwise β including in production, where it never appears.
Reserved values are real, format-valid numbers for their jurisdiction. That is deliberate: it means an upstream format rule β such as the one remittance sender creation applies before calling this check β passes the value through instead of rejecting it first, which is what lets
99900002reach us and come backfalse.The consequence is that where the reserved values are not enabled, they return the verdict the real format rules give them β
valid: truefor every value except the ITIN, which returnsfalse. A reserved value can never produce a stricter outcome than an ordinary number would, so leaving one in code is not a safety risk β but it will silently stop simulating, so do not carry one into production.
912891234is the only value that returnsfalsewith simulation off. No voided ITIN specimen exists the way078-05-1120does for SSNs, so it is drawn from a group the IRS reserves for other programmes β the only way to guarantee it can never belong to a real person. The trade is that a strict upstream format rule may reject it before it reaches this check. If that happens on your side, use it against this endpoint directly rather than through a caller that pre-validates.
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. Then add three that catch the ones you will only hit later: a stalet=outside your tolerance, a header carrying an extrav2=your code does not implement (it must still verify onv1), and a header repeatingv1=twice (it must be rejected outright, not resolved by picking one). - 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
notifiedAt. - 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 notifiedAt | 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/{sessionId} is authoritative |
Next Steps
- Getting Started β the end-to-end walkthrough
- Receiving Results β signatures, retries, and ordering
