Changelog
What changed in the Xident verification API, webhooks, and SDKs. Newest first — new entries are added above older ones, and nothing here is rewritten after the fact.
2026-09
Data match (additive)
2026-09-05
Send the identity data you already hold about a user at init — expected with any of
first_name, last_name, date_of_birth, document_number,
nationality — and the result reports, per field, whether the presented document agrees:
checks.data_match with match, mismatch or not_on_document.
A second parameter, mismatch_policy, chooses between reporting a mismatch (default) and routing
the session to your review queue.
This is an additive change to the 2026-08-13 shape: data_match is present only
when you supplied expected and a document was read, so existing integrations see no difference.
Details in Concepts → Data match; the four SDKs read the new field from
their next release.
2026-08
Dated API versions
2026-08-13
Every response and webhook shape is now pinned to a dated API version. Each of your websites carries a version, and the shape of that version never changes — no renamed field, no removed field, no changed type. When we need a change that would break an existing integration, we publish a new dated version and yours keeps working.
Nothing you need to do today. The first version, 2026-08-13, is the shape
the API already served, and every existing website was pinned to it automatically.
What is new and available to you:
- Send
X-API-Version: 2026-08-13on a request to override your website's pin — this is how you trial a new version before committing to it. - Every response echoes the version it was served under, in the
X-API-Versionheader and inmeta.api_version— on errors too. - Pick a version per website under Website Settings in the dashboard.
- An unknown or malformed version is rejected outright
(
INVALID_API_VERSION/UNKNOWN_API_VERSION) rather than quietly rounded to something near it. - The 3.1.0 SDKs send the version they were built against, so their result types always match the payload they receive.
One value changed on the wire: the webhook envelope's
api_version now reads 2026-08-13 instead of 2026-08.
The old value named a shape that had already moved on, so a consumer switching on it would
have picked the wrong parser. If you branch on that field, expect the dated form.
Full detail on API versioning, and the released versions on API versions.
The result field verification_mode is now verification_type
2026-08-13
Breaking change to a published response field. In the verification
result and in every webhook's data, the field previously called
verification_mode is now called verification_type. The values are
unchanged: full, age_check, xident_id, eu_wallet.
The old name collided with the request parameter of the same name. Those are two different things, and one name for both was the source of a good deal of confusion:
| Field | Values | |
|---|---|---|
| Request (init) — unchanged | verification_mode | auto · document · facial |
| Response and webhooks — renamed | verification_type | full · age_check · xident_id · eu_wallet |
The request parameter steers which methods run. The response field reports
which path the verdict actually came from. If you send
verification_mode when starting a verification, nothing changes for you. If you
read it from a result or a webhook, read verification_type instead, and upgrade to
the 3.0.0 SDKs.
This is the last unversioned breaking change to the verification API. From the next release, every response and webhook shape is pinned to a dated API version, older versions keep working, and a change like this one arrives as a new version you opt into rather than as a rename under your feet.
verification_mode now reports the real verification path
2026-08-12
verification_mode in the tenant result (and every webhook
data) previously returned "full" for every
session, whatever actually happened — a returning user reusing their Xident
ID, a browser-only age check and a full document verification all reported
the same value. It now reports the path that produced the verdict:
full— document path: OCR and/or a document-to-selfie face match.age_check— browser-only: liveness and/or an age-bracket model, no document.xident_id— a returning user satisfied the gate from a bracket already verified on their Xident account.eu_wallet— an EU Digital Identity Wallet presentation.
The value is derived from the evidence the session actually recorded, so it
cannot disagree with what you were billed for. Treat the set as
open and always handle a default — new paths will be added here as
they ship. The checks object remains the source of truth for
exactly which methods ran; verification_mode is the one-word
summary of which path they belonged to.
If your integration branches on verification_mode == "full" to
mean "a verification happened", switch to verified (the single
boolean for access control) or to the specific
checks entry you care about. Sessions that genuinely ran the
document path still report full, unchanged. The previous value
"token" was never emitted by the live API and has been renamed
to xident_id.
New optional risk object in the v1 result
2026-08-06
The tenant result (API response and webhook data) gains an
optional top-level risk object:
{"band": "low" | "elevated" | "high"} — a coarse
fraud assessment derived from document and capture analysis the pipeline
already performs (document forensics, duplicate-image detection,
capture-provenance checks, and related signals). It appears on
document-path verifications; when no such signals ran — browser-only age
checks, wallet verifications — the key is omitted entirely, and an absent
risk is a weaker claim than "low". The band is a
closed set you may switch on; any future addition would be announced here
first. Deliberately no numeric score on the API: precise scores let a
fraudster iterate a forgery against the number, so the detail lives in the
dashboard review UI instead. A high band never auto-fails a
session — it informs your review decisions. Additive change under the v1
freeze; consumers that ignore unknown keys need no changes.
New checks.aml entry in the v1 result
2026-08-06
The checks object gains an aml entry — sanctions
and politically-exposed-persons screening of the document-extracted
identity, same {performed, passed} shape as the
other checks. It reports performed: false until screening is
enabled for your plan. A strong screening match never auto-rejects a
verification: it routes the session to your review queue with reason
aml_match. Additive change under the v1 freeze; consumers
that ignore unknown keys need no changes.
New checks.eu_wallet entry in the v1 result
2026-08-06
The v1 result's checks object gains a fifth entry,
eu_wallet — the EU Digital Identity Wallet presentation, with
the same {performed, passed} shape as the other
checks. Sessions verified via a wallet previously reported the other four
checks performed: false. This is an additive change under the
v1 freeze: the existing keys, their order, and their meaning are untouched,
and consumers that ignore unknown keys need no changes. Wallet-verified
sessions also now meter as a Check (the cheap operation
class) — they were previously not metered at all.
New optional field ip_country; checks.document.country corrected
2026-08-04
The tenant result (API response and webhook data) gains one new optional top-level field: ip_country — the country the end user connected from, derived from their IP address at session creation. This is the first additive change under the v1 freeze: nothing existing was renamed, removed, or retyped, so no integration changes are required and existing parsers are unaffected.
Alongside it, a bug in checks.document.country is fixed: it previously carried that same IP-derived country. It now reports what its name says — the issuing country extracted from the presented document — and is omitted when no document was processed. If you were relying on it for the user's location, read ip_country instead.
Verification result v1 — pinned
2026-08
What changed
The verification result returned by the API has been simplified to a small, verdict-only object — token, status, verified, reason, verification_mode, external_user_id, a checks breakdown, and three timestamps. No raw model output, no per-provider blobs. See the full field reference in the API Reference.
That same object is now delivered byte-identical as the data field of every session.* and review.* webhook — what you read from the API is exactly what lands in your webhook handler, with no separate shape to maintain. The webhook envelope's api_version has been bumped from 2026-03 to 2026-08 to mark the change.
The split: two read endpoints, two audiences
Reading a verification outcome is now explicitly two different endpoints for two different consumers:
GET /verify/v1/result/{token}— the full result above. Requires your secret key. This is what your backend calls.GET /verify/v1/status/{token}— a minimal subject-facing status (pass/fail plus a masked reason, no per-check detail, no age gate, no document or country data). This is what the in-browser widget polls — it's safe to expose to the end user's own device because it never carries anything beyond what they're entitled to see about themselves.
The promise
From this release, the v1 result and webhook schemas are frozen. Future changes to this shape are additive only — at most, new optional fields. Anything that would break an existing integration ships on a new versioned path instead, never as a change to v1. If you're integrating against the shapes documented today, cite this entry: it's the reference for what "v1" means going forward.
SDKs
The Go, Node.js, PHP, and Python SDKs are all at 2.0.0, matching this shape — see the SDK overview.
Migrating from the pre-pin shape
The result object no longer carries the old per-provider blob fields — liveness_result, age_result, ocr_result, face_match_result, country_code, and similar raw fields are gone. If your integration only reads status and reason — the documented pattern — you need no code changes beyond upgrading to the 2.0.0 SDKs. If you were reaching into one of the removed blob fields directly, switch to the corresponding entry under checks in the new result.
Example webhook delivery
A session.success delivery under the new envelope:
{
"id": "evt_a1b2c3d4e5f6",
"type": "session.success",
"api_version": "2026-08",
"created": 1785751350,
"data": {
"token": "xtk_golden0001",
"status": "success",
"verified": true,
"verification_mode": "full",
"external_user_id": "cust-4711",
"checks": {
"liveness": { "performed": true, "passed": true },
"age": { "performed": true, "passed": true, "gate": 21 },
"document": { "performed": true, "passed": true, "document_type": "passport", "country": "DE" },
"face_match": { "performed": true, "passed": true }
},
"created_at": "2026-08-03T10:00:00Z",
"completed_at": "2026-08-03T10:02:30Z",
"expires_at": "2026-08-03T10:15:00Z"
}
}
See Core Concepts → Webhooks for the full envelope, signature verification, and retry policy.