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

code
              ┌──────────────► verified
              
  pending ────┼──────────────► failed
              
              ├──────────────► not_found
              
              └──────────────► error
StatusMeaningTerminal?
pendingQueued or in progress.No
verifiedThe identity was confirmed; validations passed.Yes
failedCompleted, but validations did not pass (e.g. face mismatch, insufficient credit).Yes
not_foundThe ID number was not found in the government database.Yes
errorA 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.

reasonCodeStatusMeaningRetry?
document_unreadablefailedNo text could be read from the document photo.New photo
document_blurryfailedThe document photo was too blurry to read.New photo
document_type_mismatchfailedThe document doesn't match the selected ID type.Fix input
id_number_not_foundfailedNo ID number could be read from the document.New photo
document_data_mismatchfailedSubmitted name/DOB don't match the document.Fix input
document_expiredfailedThe document has expired.New document
selfie_mismatchfailedThe selfie doesn't match the government photo.New selfie
gov_data_mismatchfailedSubmitted details don't match the government record.Fix input
gov_validation_failedfailedThe government database flagged the ID as failed.No
identity_not_foundnot_foundThe ID number wasn't found in the government database.Fix input
unsupported_id_typeerrorThis ID type isn't supported for the country.No
provider_errorerrorA temporary provider error occurred — not charged.Yes
media_not_founderrorAn uploaded file expired before processing — not charged.Yes
insufficient_creditserrorYour credit balance was too low — not charged.After top-up
system_errorerrorAn unexpected server error occurred — not charged.Yes
id_type_not_enablederrorThis ID type isn't enabled for your organization.No
document_verification_disablederrorDocument verification is disabled for your organization.No
gov_db_check_disablederrorGovernment database checks are disabled.No
sandbox_not_foundnot_foundSandbox test ID returned the canned not_found outcome.
sandbox_failedfailedSandbox test ID returned the canned failed outcome.

When more than one check fails (e.g. selfie and data), reasonCode reflects the primary failure while reason still describes every issue. Retries must reuse the same requestId.

End-to-end flow

  1. ConfigureGET /config to learn enabled ID types and their features.
  2. Capture & upload — for document/liveness flows, POST /upload each file → mediaId.
  3. CreatePOST /verify with country, idType, optional idNumber, userData, mediaIds, and a unique metadata.requestId. Returns 202 + verificationId, status pending.
  4. Settle — the platform fires verification.started, then exactly one terminal event (verification.completed, .failed, .not_found, or .error).
  5. Reconcile — update your records from the webhook (or a GET /status/:id poll).

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 requestId per 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 reasonCode to tailor the user's next step (e.g. "your ID has expired" on document_expired); surface reason to 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 the GET /status/:id response, which returns result.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 error without persisting the submitted PII, and any uploaded media is discarded immediately.