U-verify

Documentation

Errors & status codes

Every error U-verify returns is machine-readable and has a human sentence attached; you should never have to guess what went wrong from an HTTP status code alone.

The error envelope

Almost every error comes back in the same shape:

{
  "success": false,
  "error": {
    "code": "INSUFFICIENT_BALANCE",
    "message": "This project does not have sufficient wallet balance to run this verification.",
    "balance": 150,
    "cost": 200
  }
}
  • Show error.message to your users where relevant, it's already written to be understood by someone who isn't a developer.
  • Branch your own logic on error.code, not error.message : the wording can be improved over time, the code is stable.
  • Validation errors additionally carry error.details : a map of { field: ["message", ...] }.

A few older endpoints differ slightly

/auth/token, the phone verification endpoints, and the Paystack webhook receiver return a flat { "success": false, "message": "..." } (no nested error object, no code). Handle both shapes defensively: try error.message, then fall back to a top-level message.

Quick Verify's per-check failures don't use this envelope either

POST /verify returns 200 with success: true even when one of the checks you asked for failed, a NIN typo shouldn't 4xx a request that also successfully ran a BVN check. Look at each entry under data.checks instead: status: "failed" plus a human message, no code field, there's nothing to branch program logic on per-check, only text to show the user.INSUFFICIENT_BALANCE is the one exception: it still stops the whole request with the standard envelope, at 402.

Authentication & authorization

CodeHTTPMeaningWhat to do
MISSING_HEADERS401X-Client-Key, X-Timestamp, or X-Signature wasn't sent.Check your signing code attaches all three headers on every request.
INVALID_CLIENT_KEY401The client_key doesn't exist, or your project is suspended.Double-check the key, or check your dashboard for a suspension notice.
TIMESTAMP_DRIFT401X-Timestamp is more than 5 minutes off the server's clock.Sync your server's clock via NTP, this is the #1 cause in production.
INVALID_SIGNATURE401The recomputed signature doesn't match.See the signature-mismatch checklist on the Authentication page.
REPLAY_DETECTED429The exact same signed request (client_key, timestamp, method, path, and body) was already processed once, within the last 10 minutes.Each request needs its own fresh timestamp and signature; never resend a captured request verbatim, including for manual retries. Generate a new signed request instead. Two genuinely different requests issued in the same wall-clock second (e.g. two parallel GETs) won't collide, method and path are part of what's checked.
CREDENTIAL_ERROR500The server couldn't resolve your stored secret.Not your fault; retry, then contact support if it persists.
TOKEN_MISSING401No Authorization: Bearer header on an endpoint that needs one.Attach your access_token, in addition to the HMAC headers.
TOKEN_EXPIRED401Your JWT access token's expires_in window has passed.Call /auth/token/refresh before this happens, don't wait for a 401.
TOKEN_INVALID401The token is malformed, revoked, or signed with an old key.Get a fresh token via /auth/token.
FORBIDDEN403Authenticated, but not allowed to touch this resource.You're likely trying to access another client's data, check the ID you're passing.

Validation & rate limits

CodeHTTPMeaningWhat to do
VALIDATION_ERROR422One or more fields failed validation.Read error.details; it's a map of field name → array of messages, same shape as Laravel's default validator.
NOT_FOUND404The ulid/id in the URL doesn't exist (or doesn't belong to you).Check the identifier, and remember most resources are scoped to your own client_key.
TOO_MANY_ATTEMPTS429You've hit a rate limit.Back off and retry; see the rate limit table on the API reference page. Respect it; repeated bursts can trigger a temporary IP block.

Verification flow

CodeHTTPMeaningWhat to do
INSUFFICIENT_BALANCE402Your wallet can't cover this check's cost.Top up via POST /wallet/fund/initialize, or the Wallet page in your dashboard.
CHECK_NOT_PERMITTED403Your billing plan doesn't include this check type.Contact support to upgrade your plan.
CHECK_ALREADY_PROCESSING409You submitted the same check twice while the first was still running.Poll the status endpoint instead of resubmitting.
CHECK_NOT_REQUIRED422You're submitting a check type this session didn't ask for.Match the check against required_checks from when you created the session.
CANNOT_CANCEL422The session already completed or was already cancelled.No action needed, it's already in a final state.
SESSION_EXPIRED410The verification session's TTL ran out before you finished it.Start a new session, treat this as a normal retry path, not an alarming error.
SESSION_NOT_FOUND404No session matches that session_id.Check the ID, sessions from the hosted flow are single-use.
LIVENESS_FAILED422Face Collections authenticate/search: the passive anti-spoofing check didn't pass before the identity match ever ran.Re-capture with better lighting and make sure it's a live selfie, not a photo or screen.
DOCUMENT_COMPARE_FAILED422POST /face-document-compare couldn't complete the comparison.Check both images are clear, well-lit, and actually contain a face/ID photo.

Hosted verification

CodeHTTPMeaningWhat to do
INVALID_REDIRECT_URL400redirect_url isn't on a domain registered against this client_key.Register the domain (including staging) from your dashboard, or ask support.
CLIENT_INACTIVE400The project this client_key belongs to is suspended.Contact support.
CALLBACK_SIGNING_FAILED500U-verify couldn't sign the result, an internal issue.Not your fault, retry the flow, contact support if it repeats.
RESULT_NOT_FOUND404No stored result for that session_id / reference.Double-check you're passing the exact session_id or reference from the original flow.

Payments

CodeHTTPMeaningWhat to do
PAYSTACK_ERROR502Paystack itself failed to initialize the transaction.Retry in a moment,this is upstream, not something you can fix by changing your request.
PAYMENT_FAILED402The wallet top-up transaction wasn't successful.The user should retry payment, no funds were moved.
PAYMENT_NOT_VERIFIED402Paystack hasn't confirmed the public-search payment as successful yet.You haven't been charged, try the search again.
UNDERPAYMENT402The amount paid doesn't match the check's fee.Shouldn't happen through the normal UI, contact support if you see this.
PROVIDER_FAILED502Payment succeeded but the government/bank source didn't return a result.You'll be refunded, contact support with the reference if it doesn't happen automatically.

Still stuck?

If you're getting an error that isn't listed here, or the fix above didn't work, don't spend hours guessing, reach a real person with the exact error.code and, if you can, the X-Timestamp of the failing request.