Identity API
Reference

Hosted verification sessions

Send a customer a link. They match their ID against the national register and pass a camera liveness check on a Sendexa-hosted page, and you get the result.

How it works

  1. Your server creates a session and gets back a verificationUrl.
  2. You send that link to the customer by SMS, email, WhatsApp or in your app. Sendexa doesn't send it for you.
  3. The customer sees a consent screen with your business name, logo and privacy-policy link (set the link under Hosted Sessions → Settings). Nothing is collected until they agree.
  4. If you asked for an ID check, the customer enters their Ghana Card or Voter ID details and we match them against the register. They get up to 3 attempts.
  5. They photograph their card (front, and back for a Ghana Card). On a laptop they can scan a QR code to continue on their phone.
  6. The camera coaches them into the oval and takes a selfie. During each liveness gesture the screen briefly flashes colours the server picked at random; the server checks the camera frames show the face reflecting exactly those colours, which a replayed video can't do.
  7. You receive a VERIFICATION_COMPLETED webhook, or poll the session. If you set a redirectUrl, the customer is sent back to it with ?sessionId=…&status=….

POST/v1/verify/sessions

Create a session. Works with any valid API key.
FieldTypeDescription
contactrequiredstringThe customer's phone number or email. Stored for your records; we don't message it.
contactTyperequiredstringPHONE or EMAIL
idDocumentstringGHANA_CARD or VOTER_ID to add the ID step. Leave it out for a liveness-only session.
purposestringUp to 200 characters, shown to the customer, e.g. "opening your account".
challengesstring[]Which gestures to ask for: BLINK, SMILE, LOOK_LEFT, LOOK_RIGHT, LOOK_UP, LOOK_DOWN. Defaults to all six.
ttlMinutesnumberHow long the link stays valid, 5 to 60. Default 20.
redirectUrlstringhttps link the customer returns to when they finish. We append sessionId and status (COMPLETED or FAILED). Treat it as a UX hint and confirm the result by webhook or GET.
Bash
curl -X POST 'https://api.sendexa.co/v1/verify/sessions' \
-H 'Authorization: Basic <YOUR_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{
"contact": "+233241234567",
"contactType": "PHONE",
"idDocument": "GHANA_CARD",
"purpose": "opening your account",
"challenges": ["BLINK", "LOOK_LEFT", "SMILE"],
"redirectUrl": "https://acme.com/onboarding/verified"
}'

Returns 201:

JSON
{
"success": true,
"data": {
"sessionId": "q8V3nX0bTzK2mR7wYp1cHg",
"verificationUrl": "https://dash.sendexa.co/v/q8V3nX0bTzK2mR7wYp1cHg",
"challenges": ["BLINK", "LOOK_LEFT", "SMILE"],
"idDocument": "GHANA_CARD",
"redirectUrl": "https://acme.com/onboarding/verified",
"expiresAt": "2026-10-12T10:20:00.000Z"
}
}

GET/v1/verify/sessions/:sessionId

Fetch a session's current state.
JSON
{
"success": true,
"data": {
"sessionId": "q8V3nX0bTzK2mR7wYp1cHg",
"contact": "+233241234567",
"contactType": "PHONE",
"purpose": "opening your account",
"status": "COMPLETED",
"challenges": ["BLINK", "LOOK_LEFT", "SMILE"],
"completedChallenges": ["BLINK", "LOOK_LEFT", "SMILE"],
"idDocument": "GHANA_CARD",
"idVerified": true,
"identityCheckId": "IDN-5f0c2a9e-7c1b-4f7e-9d2a-0b6c1e8f3a21",
"failReason": null,
"consentAt": "2026-10-12T10:01:02.000Z",
"riskFlags": [],
"reviewedByUserId": null,
"reviewedAt": null,
"reviewNote": null,
"redirectUrl": "https://acme.com/onboarding/verified",
"images": [
{ "id": "cm2x9a1b20003", "kind": "ID_FRONT", "createdAt": "2026-10-12T10:02:40.000Z" },
{ "id": "cm2x9a1b20004", "kind": "ID_BACK", "createdAt": "2026-10-12T10:03:05.000Z" },
{ "id": "cm2x9a1b20005", "kind": "SELFIE", "createdAt": "2026-10-12T10:03:51.000Z" }
],
"cost": 0.15,
"currency": "USD",
"expiresAt": "2026-10-12T10:20:00.000Z",
"completedAt": "2026-10-12T10:04:31.000Z",
"createdAt": "2026-10-12T10:00:00.000Z"
}
}
FieldTypeDescription
statusstringSee the table below.
idVerifiedboolean | nullnull until the customer submits ID details; then whether they matched.
identityCheckIdstring | nullThe KYC check run for the ID step. Fetch it from GET /v1/identity/checks/:checkId (needs identity.read) to see the matchScore.
failReasonstring | nullWhy a FAILED session failed. See below.
consentAtstring | nullWhen the customer accepted the consent screen.
imagesobject[]Photos collected (ID_FRONT, ID_BACK, SELFIE). They're stored encrypted and can be viewed by your team in the dashboard, where each view is logged. They're deleted after your retention period (default 90 days).
costnumberThe session fee charged; 0 unless the session completed. ID-step charges are separate KYC checks.

GET/v1/verify/sessions

List your sessions, newest first. Query parameters: limit (default 50, max 100) and offset. Returns { sessions, total, limit, offset }.

POST/v1/verify/sessions/:sessionId/review

Approve or reject a session in NEEDS_REVIEW from your own back office. Your team can do the same in the dashboard. The first decision wins; a second gets 409. The webhook fires after the decision.
FieldTypeDescription
decisionrequiredstringAPPROVE or REJECT
notestringWhy. Required when rejecting; up to 1000 characters. Returned as reviewNote.
Bash
curl -X POST 'https://api.sendexa.co/v1/verify/sessions/q8V3nX0bTzK2mR7wYp1cHg/review' \
-H 'Authorization: Basic <YOUR_TOKEN>' \
-H 'Content-Type: application/json' \
-d '{ "decision": "REJECT", "note": "Selfie does not match the card photo" }'

Statuses

ValueMeaning
PENDINGCreated; the customer hasn't started.
IN_PROGRESSThe customer has started (submitted ID details or completed a gesture).
NEEDS_REVIEWEvery step passed, but a risk flag needs your decision. Approve or reject it in the dashboard or with the review endpoint below.
COMPLETEDEvery step passed (or you approved it in review). The customer is verified.
FAILEDA step failed. failReason says which.
EXPIREDThe link timed out before the customer finished.

Risk flags

In riskFlags. Any flag holds the session at NEEDS_REVIEW instead of completing it.

ValueMeaning
LOW_FLASH_RESPONSEThe customer's face barely reflected the screen-flash colours. Often bright surroundings; sometimes a replayed or injected video. Check the selfie and card photos.
ID_PARTIAL_MATCHThe ID matched, but the register's name-match score was under 100%. Check the names against the card photo.

Fail reasons

ValueMeaning
ID_MISMATCHThe ID details didn't match the register (or the register couldn't confirm them).
REGISTRY_UNAVAILABLEThe register couldn't be reached on any of the customer's 3 attempts.
BILLING_FAILEDYour wallet balance was too low to run the ID check or pay the session fee.
STATIC_FEEDThe camera picture didn't change between frames: a photo or a frozen feed.
REJECTED_IN_REVIEWYour team (or your server) rejected it in review. reviewNote says why.
any other textA liveness failure, in plain English, e.g. "No blink detected".

A session only reaches COMPLETED if the ID step passed (when there is one), every gesture passed, and either nothing was flagged or you approved it. Treat anything else as not verified.

Next

Get the result by webhook →