Verification lifecycle
A verification is asynchronous. Creating one returns instantly with pending; the platform then runs the checks and settles on a final status, which you learn via webhooks or polling.
States
┌──────────────► verified
│
pending ────┼──────────────► failed
│
├──────────────► not_found
│
└──────────────► error| Status | Meaning | Terminal? |
|---|---|---|
pending | Queued or in progress. | No |
verified | The identity was confirmed; validations passed. | Yes |
failed | Completed, but validations did not pass (e.g. face mismatch, insufficient credit). | Yes |
not_found | The ID number was not found in the government database. | Yes |
error | A system error occurred during processing. | Yes |
A non-success status (failed, not_found, error) always carries a human-readable reason (a full sentence, safe to show a user) and a stable reasonCode you can branch on in code. Both are null on verified. See failure reason codes below.
Failure reason codes
reasonCode is a stable machine token paired with reason on every non-success outcome. Branch on the code to drive tailored messaging or retry logic; show or log the reason for detail. It appears in status responses and webhook payloads (data.reasonCode).
New codes may be added over time, so treat an unrecognised code as a generic failure of its status.
reasonCode | Status | Meaning | Retry? |
|---|---|---|---|
document_unreadable | failed | No text could be read from the document photo. | New photo |
document_blurry | failed | The document photo was too blurry to read. | New photo |
document_type_mismatch | failed | The document doesn't match the selected ID type. | Fix input |
id_number_not_found | failed | No ID number could be read from the document. | New photo |
document_data_mismatch | failed | Submitted name/DOB don't match the document. | Fix input |
document_expired | failed | The document has expired. | New document |
selfie_mismatch | failed | The selfie doesn't match the government photo. | New selfie |
gov_data_mismatch | failed | Submitted details don't match the government record. | Fix input |
gov_validation_failed | failed | The government database flagged the ID as failed. | No |
identity_not_found | not_found | The ID number wasn't found in the government database. | Fix input |
unsupported_id_type | error | This ID type isn't supported for the country. | No |
provider_error | error | A temporary provider error occurred — not charged. | Yes |
media_not_found | error | An uploaded file expired before processing — not charged. | Yes |
insufficient_credits | error | Your credit balance was too low — not charged. | After top-up |
system_error | error | An unexpected server error occurred — not charged. | Yes |
id_type_not_enabled | error | This ID type isn't enabled for your organization. | No |
document_verification_disabled | error | Document verification is disabled for your organization. | No |
gov_db_check_disabled | error | Government database checks are disabled. | No |
sandbox_not_found | not_found | Sandbox test ID returned the canned not_found outcome. | — |
sandbox_failed | failed | Sandbox test ID returned the canned failed outcome. | — |
When more than one check fails (e.g. selfie and data),
reasonCodereflects the primary failure whilereasonstill describes every issue. Retries must reuse the samerequestId.
End-to-end flow
- Configure —
GET /configto learn enabled ID types and their features. - Capture & upload — for document/liveness flows,
POST /uploadeach file →mediaId. - Create —
POST /verifywithcountry,idType, optionalidNumber,userData,mediaIds, and a uniquemetadata.requestId. Returns202+verificationId, statuspending. - Settle — the platform fires
verification.started, then exactly one terminal event (verification.completed,.failed,.not_found, or.error). - Reconcile — update your records from the webhook (or a
GET /status/:idpoll).
Idempotency
metadata.requestId is your idempotency key. Submitting the same requestId again returns the existing verification rather than creating a new one (and never double-charges). Always:
- Generate one stable
requestIdper logical verification. - Reuse it verbatim on any retry (timeout,
5xx,429).
See create verification.
Designing your handler
- Treat results as eventual. Never block a user flow on a synchronous result — show a "verifying…" state and resolve on webhook/poll.
- Be idempotent on your side too. The same terminal event may be delivered more than once; key your processing on
verificationId. - Map statuses to outcomes explicitly:
verified→ pass;failed/not_found→ reject or request retry;error→ retry or escalate. - Branch on
reasonCodeto tailor the user's next step (e.g. "your ID has expired" ondocument_expired); surfacereasonto your support team for detail. See failure reason codes.
Data & privacy notes
- ID numbers are stored as plaintext (
idNumber) so they appear unmasked in your dashboard and in webhook payloads (data.idNumber). The only place they're masked is theGET /status/:idresponse, which returnsresult.idNumberMasked(e.g.1234•••901) — that endpoint is designed for safe polling from frontends, so it never echoes the full number back to whoever holds the API key. - If an ID type or feature is not enabled for your org, the attempt is recorded as
errorwithout persisting the submitted PII, and any uploaded media is discarded immediately.