verification_url, and receive the Kayle result over a webhook when the user finishes, or a verification.session.expired event if they don’t.
The session object
The shape returned by the API:cancel_token is returned only at creation. The verify URL embeds it as a query parameter so the verify browser and native apps can pass it back when cancelling. If you want to surface a cancel link from somewhere else (a dashboard, an email), store it server-side at creation time.
Lifecycle
A session moves through these statuses:| Status | Meaning |
|---|---|
created | The session exists. The mobile client has not authenticated yet. |
in_progress | A mobile client is actively performing the Kayle check. |
succeeded | All Kayle checks confirmed and the user consented to share at least all required fields. |
failed | A check exhausted its retry budget, or a hard-kill code (anti-cloning attestation) terminated the session. |
expired | 60 minutes elapsed since creation without a terminal outcome. |
cancelled | The session was cancelled by you or by the user. |
created_at. A scheduled job sweeps expired sessions and emits verification.session.expired for any that didn’t reach a terminal status first.
Retries
A session has per-check retry budgets — not a single whole-session budget. When a user fails NFC, they don’t have to re-scan MRZ; when they fail liveness, they don’t have to re-tap the chip.| Check | Budget |
|---|---|
| MRZ scan | Unlimited (purely client-side OCR; the server never sees a failed MRZ scan) |
| NFC chip read | 3 retries |
| Liveness capture | 3 retries |
verification.session.failed payload’s data.nfc_tries_used and data.liveness_tries_used tell you which check exhausted.
document_anti_cloning_attestation_failed is a hard-kill: the session terminates immediately, regardless of retry counters.
The session response exposes the live counters on nfc_tries_used and liveness_tries_used so you can render an “attempts remaining” UI without subscribing to webhooks.
Creating a session
share_fields is optional. If you omit it, the session uses Kayle’s default share contract (a minimal set including kayle_document_id). If you include it, every entry needs required and a non-empty reason (200 characters max). See Share fields for the catalogue.
redirect_url is also optional, but it must begin with https://.
Both fields are reflected back to the user during the consent step, so write reason strings the user will see and trust.
Cancelling
Two cancel paths exist:- Authenticated cancel —
POST /v1/sessions/:id/cancelwith your API key. Idempotent. Use this when you decide server-side that you no longer want the Kayle check to proceed. - Public cancel —
POST /v1/verify/session/:id/cancelwith the one-shotcancel_tokenin the body. The verify app and native apps use this to abort from the user’s side. The token is consumed on first successful use; subsequent calls return204if the session is already terminal, otherwiseCANCEL_TOKEN_USED.
verification.session.cancelled to subscribed webhooks. The payload carries a reason (cancelled, cancelled_after_failed_check, or one of the privacy_cancelled_after_terminal_* variants when the event replaces a scrubbed succeeded / failed payload) plus the per-check retry counters. See the reference for the full field list.
The
verification_url in the create response already contains cancel_token as a query parameter, so you don’t need to surface it yourself unless you’re building a separate cancel UI.Listing sessions
GET /v1/sessions supports filtering by status, created_from, and created_to, and follows the standard cursor pagination (limit, starting_after, next_cursor). Pass include_attempts=true to attach the attempt array to each session — handy for reporting, but increases payload size.
What you don’t see
The session object never includes raw document data, MRZ fields, the on-chip portrait, selfies, or the face match score. Those are computed and discarded inside the verification pipeline. The consented claims surface only in theverification.session.succeeded webhook payload.