Перейти к содержимому
Zoi ID

Разрабатывайте с Zoi ID

Два API на одном хосте, типизированные клиенты на шести языках, размещённый сценарий и песочница. Полное руководство открыто уже сейчас, а ключи песочницы выдаются вместе с ранним доступом.

Базовый URLhttps://kyc.zoi.network

Быстрый старт

  1. 1

    Создайте сессию

    Ваш бэкенд вызывает POST /v1/partner/sessions со своим партнёрским ключом.

  2. 2

    Запустите сценарий

    Передайте клиентский токен в SDK или откройте verificationUrl в браузере.

  3. 3

    Получите результат

    Проверьте подпись вебхука, затем сохраните идентификатор личности и её статус.

shell
curl https://kyc.zoi.network/v1/partner/sessions \
  -H "Authorization: Bearer $ZOI_ID_PARTNER_KEY" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: signup-user_42" \
  -d '{ "externalUserId": "user_42", "level": "standard", "appId": "com.example.app" }'

Два API

Партнёрский API

Сервер к серверу, по пути /v1/partner. Передавайте партнёрский ключ как Bearer-токен. Никогда не кладите его в приложение.

Клиентский API

Используется SDK, по пути /v1/client. Он передаёт клиентский токен, который действует для одной сессии 60 минут.

Соглашения

  • Время задаётся в миллисекундах epoch, в полях, имя которых оканчивается на At.
  • Даты в формате YYYY-MM-DD. Неизвестные части пишутся как XX, например 1990-XX-XX.
  • POST-запросы, которые что-то создают или решают, принимают заголовок Idempotency-Key. Ключи хранятся 24 часа.
  • Изменения сессии передают ожидаемую ревизию. Если она изменилась, вы получите revision_conflict.

Ход сессии

Ваш бэкенд создаёт сессию. SDK выполняет шаги. Сервер следит за порядком и отвечает step_not_allowed, если шаг пришёл слишком рано.

1. Создайте сессию на бэкенде

Повторный вызов для того же пользователя продолжает открытую сессию и выдаёт новый клиентский токен.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: 6f1c2a9e-signup-42

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "clientTokenExpiresAt": 1767225600000,
  "status": "created",
  "apiBaseUrl": "https://kyc.zoi.network",
  "resumed": false
}

2. Шаги, которые выполняет SDK

  1. 01

    Прочитать сессию и оставшиеся шаги.

    GET /v1/client/session

  2. 02

    Записать согласие, вместе с отдельным необязательным выбором о сравнении лица.

    POST /v1/client/consent

  3. 03

    Проверить приложение и устройство. Результат учитывается в решении.

    POST /v1/client/attestation/challenge, POST /v1/client/attestation

  4. 04

    Отправить документ: прочитать чип после сканирования MRZ или отправить фото.

    POST /v1/client/document/mrz-scan, POST /v1/client/document/chip/challenge, POST /v1/client/document/chip or POST /v1/client/document/photos

  5. 05

    Записать одно короткое видео фронтальной камерой, пока приложение показывает движения головы.

    POST /v1/client/liveness/challenge, POST /v1/client/liveness

  6. 06

    Отправить страну проживания, а на расширенном уровне и адрес.

    POST /v1/client/profile

  7. 07

    Только с требованием адреса: загрузить документ, затем подтвердить адрес. Пропускается, если адрес уже дал документ, удостоверяющий личность.

    POST /v1/client/address/document, POST /v1/client/address/confirm

  8. 08

    Только с требованием по средствам: сохранить ответы, добавить документы, если этого требует политика, затем завершить шаг.

    POST /v1/client/funds/answers, POST /v1/client/funds/document, POST /v1/client/funds/remove, POST /v1/client/funds/complete

  9. 09

    Ответить на два вопроса: является ли человек публичным должностным лицом и действует ли от своего имени.

    POST /v1/client/declarations

  10. 10

    Отправить с текущей ревизией.

    POST /v1/client/submit

Пользователь также может попросить человека пересмотреть автоматический результат (POST /review-request) или отозвать согласие, что отменяет сессию и сразу удаляет её файлы (POST /withdraw).

Подтверждение адреса и источника средств

Добавьте requirements в POST /v1/partner/sessions, чтобы проверить не только личность. address запрашивает подтверждение адреса. source_of_funds спрашивает, откуда деньги, а source_of_wealth также спрашивает об общем состоянии и о том, как оно сложилось. Оба значения по средствам вместе образуют один шаг, и оба сочетаются с address.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "appId": "com.example.app",
  "requirements": ["address", "source_of_funds"],
  // Optional. Without them your partner default applies, else ours.
  "addressPolicy": { "maxAgeDays": 90, "signedOnly": false },
  "fundsPolicy": { "evidence": "optional", "currency": "EUR" }
}

Необязательные политики

Без политики действуют ваши партнёрские настройки по умолчанию, а если их нет, наши. Политика копируется в сессию при её создании.

  • addressPolicy: maxAgeDays (по умолчанию 90, от 1 до 366), acceptedTypes (по умолчанию: коммунальные, телеком- и банковские документы, налоговые документы, письма госорганов и справки о месте жительства) и signedOnly (по умолчанию false).
  • fundsPolicy: questions, evidence (none, optional или required, по умолчанию none), acceptedTypes, maxAgeDays (по умолчанию 180), currency (ISO 4217, по умолчанию USD) и brackets для диапазонов.
  • Открытая сессия с другими требованиями, которая ещё не отправлена, отменяется, и начинается новая, как при смене уровня.
  • Проверьте каждый исход на тест-кейсах песочницы, например address_signed_pdf, address_document_review, funds_signed_documents и funds_review.

Дополнительные сессии

Проверяйте только требования для пользователя, чья личность у вас уже активна. Передайте reuseIdentity: true вместе с requirements.

  • Пользователь даёт согласие и проходит только новые шаги. Документ, селфи и декларации повторно не запрашиваются.
  • При одобрении личность сохраняет идентификатор, дату проверки и срок действия и получает новый результат. Вы получаете session.approved и identity.updated.
  • Без активной личности вызов возвращает 409 step_not_allowed.
  • Запускайте её по своим правилам риска, например при крупном депозите, отметке PEP или стране повышенного риска. Ваши правила остаются в вашем бэкенде, в одном месте.
POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: edd-user_42-2026-11

{
  "externalUserId": "user_42",
  "appId": "com.example.app",
  "requirements": ["source_of_funds", "source_of_wealth"],
  "reuseIdentity": true,
  "fundsPolicy": { "evidence": "required" }
}

// No active identity for user_42:
409 { "error": { "code": "step_not_allowed", ... } }

Результаты и разрешения

Одобренные сессии содержат address и funds, а личность хранит последний результат по каждому. Поля сводки приходят с любым разрешением. Для деталей нужно разрешение.

  • Разрешение address: строки адреса. Без него вы получаете verified, method, evidenceLevel, documentType, documentDate, verifiedAt и country.
  • Разрешение financial_data: answers, documents и findings. Без него вы получаете kinds, evidenceLevel, reviewed, documentCount и verifiedAt. Оно никогда не входит в eligibility.
  • evidenceLevel принимает значения high, medium, low или none. Способы подтверждения адреса, от самого сильного: wallet, id_document, signed_pdf, official_code, document_review и declared.
  • Документы не передаются через API. Участники с ролью комплаенса могут смотреть страницы документов об источнике средств в панели, если у окружения есть разрешение financial_data. Каждый просмотр записывается.
GET /v1/partner/sessions/:id
"session": {
  "id": "ses_...",
  "status": "approved",
  "requirements": ["address", "source_of_funds"],
  "reusesIdentity": false,
  "address": {
    "verified": true,
    "method": "signed_pdf",
    "evidenceLevel": "high",
    "documentType": "bank_statement",
    "documentDate": "2026-10-14",
    "verifiedAt": 1767225600000,
    "country": "DE",
    // scope "address"
    "address": { "line1": "Musterstrasse 12", "city": "Berlin", "postalCode": "10115", "country": "DE" }
  },
  "funds": {
    "kinds": ["source_of_funds"],
    "evidenceLevel": "medium",
    "reviewed": true,
    "documentCount": 2,
    "verifiedAt": 1767225600000,
    // scope "financial_data"
    "answers": {
      "employmentStatus": "employed",
      "income": { "min": 50000, "max": 100000, "currency": "EUR" },
      "sources": ["salary", "savings"],
      ...
    },
    "documents": [
      { "documentType": "payslip", "documentDate": "2026-10-31", "amount": 5200, "currency": "EUR",
        "signature": "valid", "signedByKnownIssuer": true, "nameMatch": "match", "findings": [], ... },
      ...
    ],
    "findings": []
  }
}

Веб-сценарий

Нет SDK? Отправьте пользователя на нашу страницу. Она использует тот же API, те же проверки и ту же очередь ручной проверки.

1. Получите ссылку

POST /v1/partner/sessions возвращает verificationUrl. Передайте redirectUrl, чтобы вернуть пользователя к вам в конце. Адрес должен начинаться с одного из ваших зарегистрированных источников перенаправления.

POST /v1/partner/sessions
POST /v1/partner/sessions
Authorization: Bearer <partner key>
Content-Type: application/json

{
  "externalUserId": "user_42",
  "level": "standard",
  "locale": "en",
  "appId": "com.example.app",
  "redirectUrl": "https://example.com/kyc/done"
}

200 OK
{
  "sessionId": "ses_...",
  "clientToken": "...",
  "verificationUrl": "https://kyc.zoi.network/verify/#token=...&lang=en",
  ...
}
  • Клиентский токен находится во фрагменте URL, после #. Браузеры никогда не отправляют фрагмент на сервер, поэтому токен не попадает в логи и в заголовок Referer. Страница читает его, удаляет из адресной строки и хранит только в памяти.
  • Добавьте lang во фрагмент, чтобы выбрать один из 19 языков. Без него страница использует язык браузера.
  • При перезагрузке токен теряется. Создайте на своём бэкенде новую ссылку для той же сессии.
  • Страница отправляет channel: "web" вместе с согласием. Браузеры не умеют читать чипы документов, поэтому веб-сессии используют фото документа или цифровой ID-кошелёк. Они пропускают проверку устройства, а фото документов из веба всегда решает обученный проверяющий.
  • На компьютере страница показывает QR-код, чтобы пользователь продолжил на телефоне.

2. Когда пользователь закончил

Когда сценарий заканчивается (отправлено, отменено или финальный статус), страница отправляет это сообщение каждому из этих объектов, который существует: window.ReactNativeWebView, window.webkit.messageHandlers.zoiId, window.ZoiIdAndroid и window.opener. Затем она открывает redirectUrl с добавленными zoiIdSession и zoiIdStatus.

Никогда не доверяйте сообщению или параметрам перенаправления. Доверяйте только своему вебхуку или GET /v1/partner/sessions/:id.

postMessage
// Sent to each bridge that exists:
//   window.ReactNativeWebView.postMessage(JSON string)
//   window.webkit.messageHandlers.zoiId.postMessage(object)
//   window.ZoiIdAndroid.postMessage(JSON string)
//   window.opener.postMessage(object, "<origin of your redirectUrl>")
//     (popups only, and only when the session has a redirectUrl)
{
  "source": "zoi-id",
  "type": "finished",        // or "cancelled"
  "sessionId": "ses_...",
  "status": "processing"     // the session status at that moment
}

// Then, if redirectUrl was set (the page uses the one stored with the
// session, never one from the address bar):
https://example.com/kyc/done?zoiIdSession=ses_...&zoiIdStatus=processing

3. Настройки webview

Android WebView

Включите javaScriptEnabled и domStorageEnabled и установите mediaPlaybackRequiresUserGesture в false. В WebChromeClient разрешите onPermissionRequest для RESOURCE_VIDEO_CAPTURE после того, как приложение получит разрешение CAMERA. Добавьте выбор файла для запасного варианта с фото.

Kotlin
webView.settings.javaScriptEnabled = true
webView.settings.domStorageEnabled = true
webView.settings.mediaPlaybackRequiresUserGesture = false
webView.webChromeClient = object : WebChromeClient() {
  override fun onPermissionRequest(request: PermissionRequest) {
    // Only after your app holds Manifest.permission.CAMERA.
    if (request.resources.contains(PermissionRequest.RESOURCE_VIDEO_CAPTURE)) {
      request.grant(arrayOf(PermissionRequest.RESOURCE_VIDEO_CAPTURE))
    } else {
      request.deny()
    }
  }
  override fun onShowFileChooser(
    view: WebView, callback: ValueCallback<Array<Uri>>, params: FileChooserParams,
  ): Boolean {
    // Open a picker and pass the result to callback (photo fallback).
    return true
  }
}

iOS WKWebView

iOS 14.3 или новее. Установите allowsInlineMediaPlayback в true и добавьте NSCameraUsageDescription в приложение. В iOS 15 и новее разрешение на камеру можно решать в UI delegate.

Swift
let config = WKWebViewConfiguration()
config.allowsInlineMediaPlayback = true
let webView = WKWebView(frame: .zero, configuration: config)
webView.uiDelegate = self // iOS 15+: decide camera access below

func webView(_ webView: WKWebView,
             requestMediaCapturePermissionFor origin: WKSecurityOrigin,
             initiatedByFrame frame: WKFrameInfo,
             type: WKMediaCaptureType,
             decisionHandler: @escaping (WKPermissionDecision) -> Void) {
  decisionHandler(origin.host == "kyc.zoi.network" ? .grant : .deny)
}
// Info.plist: NSCameraUsageDescription

React Native WebView

Задайте mediaCapturePermissionGrantType="grant", allowsInlineMediaPlayback и javaScriptEnabled. Приложению нужно разрешение на камеру.

TSX
<WebView
  source={{ uri: verificationUrl }}
  javaScriptEnabled
  allowsInlineMediaPlayback
  mediaCapturePermissionGrantType="grant"
  onMessage={(event) => {
    const message = JSON.parse(event.nativeEvent.data);
    if (message.source === "zoi-id") {
      // Close the webview, then ask your backend for the real status.
    }
  }}
/>

iframe не поддерживаются из-за разрешений камеры и кликджекинга. Используйте вкладку браузера, всплывающее окно или webview.

Статусы

Сессия

created
Сессия создана, ничего ещё не загружено.
in_progress
Согласие получено, шаги загружаются.
processing
Отправлена, идут проверки.
review
Ждёт обученного проверяющего.
approved
Окончательно. Личность подтверждена.
rejected
Окончательно. Смотрите коды причин.
resubmit
Пользователь должен повторить шаги из requiredSteps.
expired
Окончательное решение не принято вовремя.
cancelled
Отменена партнёром, пользователем или удалением.

Личность

active
Можно использовать.
suspended
Приостановлена, например после нового совпадения с санкционным списком.
expired
Срок документа или проверки закончился. Новая сессия проверит заново.
revoked
Окончательно.

SDK

SDK для React Native (Expo) даёт типизированный API-клиент, чтение чипа по NFC и проверку устройства. Он работает без интерфейса, поэтому ваше приложение полностью управляет своими экранами и камерой. Для серверов используйте типизированные клиенты для TypeScript, Python, Go, PHP, Java и Ruby.

@zoi/id-sdk
import { ZoiIdClient, nextStep } from "@zoi/id-sdk";

// clientToken and apiBaseUrl come from your backend (POST /v1/partner/sessions).
const client = new ZoiIdClient({ baseUrl: apiBaseUrl, clientToken });

const session = await client.getSession();
switch (nextStep(session)) {
  case "consent":   /* show the notice and the biometric choice */ break;
  case "document":  /* chip read or photos */ break;
  case "liveness":  /* record the video */ break;
  case "address":   /* upload a document, then confirm the address */ break;
  case "funds":     /* answers, then documents when the policy asks */ break;
  case "submit":    await client.submit(session.revision); break;
  case "done":      /* wait for your backend to get the webhook */ break;
}

Вебхуки

Мы отправляем вебхук, когда меняется сессия или личность. Всегда проверяйте подпись, прежде чем доверять содержимому.

  • Каждый вебхук содержит заголовок Zoi-Id-Signature: t=unix seconds,v1=hex.
  • v1 это HMAC-SHA256 от текста t.raw body с вашим секретом вебхука. Используйте исходное тело, до разбора JSON.
  • Во время смены секрета заголовок содержит по одному v1 на каждый активный секрет. Принимайте вебхук, если совпал любой из них.
  • Отклоняйте его, если t отличается от текущего времени больше чем на 300 секунд.
  • Неудачные доставки повторяются в течение 72 часов. Каждая попытка подписывается заново с новым t.
  • У личностей есть revision. Применяйте событие, только если его ревизия новее сохранённой у вас.
identity.suspended
POST <your webhook URL>
Zoi-Id-Signature: t=1767225600,v1=5f2b...e9

{
  "v": 1,
  "eventId": "evt_...",
  "type": "identity.suspended",
  "createdAt": 1767225600000,
  "object": "identity",
  "externalUserId": "user_42",
  "identity": {
    "identityId": "idn_...",
    "revision": 7,
    "status": "suspended",
    "level": "standard",
    "verifiedAt": 1764547200000,
    "validUntil": 1859155200000,
    "ageOver18": true,
    "captureMethod": "nfc"
  }
}

Типы событий

  • session.processing
  • session.review
  • session.approved
  • session.rejected
  • session.resubmit
  • session.expired
  • session.cancelled
  • identity.updated
  • identity.suspended
  • identity.reinstated
  • identity.expired
  • identity.revoked

Проверка подписи на TypeScript

verify.ts
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCE_SECONDS = 300;

export function verifyZoiIdSignature(
  rawBody: string,
  header: string | null,
  secret: string,
  nowSeconds = Math.floor(Date.now() / 1000),
): boolean {
  if (!header) return false;

  let t: string | undefined;
  const signatures: Buffer[] = [];
  for (const part of header.split(",")) {
    const [key, value] = part.trim().split("=");
    if (!value) continue;
    if (key === "t") t = value;
    if (key === "v1" && /^[0-9a-f]+$/i.test(value)) signatures.push(Buffer.from(value, "hex"));
  }
  if (!t || !/^\d+$/.test(t) || signatures.length === 0) return false;
  if (Math.abs(nowSeconds - Number(t)) > TOLERANCE_SECONDS) return false;

  const expected = createHmac("sha256", secret).update(`${t}.${rawBody}`).digest();
  return signatures.some((sig) => sig.length === expected.length && timingSafeEqual(sig, expected));
}

// Example handler: read the raw body first, parse JSON only after the check.
export async function POST(request: Request) {
  const rawBody = await request.text();
  const ok = verifyZoiIdSignature(rawBody, request.headers.get("Zoi-Id-Signature"), process.env.ZOI_ID_WEBHOOK_SECRET!);
  if (!ok) return new Response("bad signature", { status: 400 });

  const event = JSON.parse(rawBody);
  // Store event.eventId to skip duplicates, then apply it if the revision is newer.
  return new Response(null, { status: 204 });
}

Проверка подписи на Go

verify.go
package zoiid

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"strconv"
	"strings"
	"time"
)

const toleranceSeconds = 300

// VerifySignature checks the Zoi-Id-Signature header against the raw body.
func VerifySignature(rawBody []byte, header, secret string, now time.Time) bool {
	var t string
	var sigs [][]byte
	for _, part := range strings.Split(header, ",") {
		key, value, ok := strings.Cut(strings.TrimSpace(part), "=")
		if !ok || value == "" {
			continue
		}
		switch key {
		case "t":
			t = value
		case "v1":
			if b, err := hex.DecodeString(value); err == nil {
				sigs = append(sigs, b)
			}
		}
	}
	ts, err := strconv.ParseInt(t, 10, 64)
	if err != nil || len(sigs) == 0 {
		return false
	}
	if d := now.Unix() - ts; d > toleranceSeconds || d < -toleranceSeconds {
		return false
	}

	mac := hmac.New(sha256.New, []byte(secret))
	mac.Write([]byte(t + "."))
	mac.Write(rawBody)
	expected := mac.Sum(nil)
	for _, sig := range sigs {
		if hmac.Equal(sig, expected) {
			return true
		}
	}
	return false
}

Лента событий

GET /v1/partner/events возвращает все события за последние 30 дней, от старых к новым. Передайте after с последним nextCursor, чтобы прочитать следующую страницу. Используйте после долгого сбоя или чтобы убедиться, что ничего не пропущено.

GET /v1/partner/events
GET /v1/partner/events?after=<nextCursor>&limit=100
Authorization: Bearer <partner key>

200 OK
{
  "items": [ /* webhook payloads, oldest first */ ],
  "nextCursor": "..."   // null when there is nothing more
}

Сообщения о мошенничестве

Сообщите нам, если решённая сессия оказалась мошеннической, например после чарджбэка. Сообщения помогают нам измерять долю ошибочных одобрений. Они не меняют сессию, личность или исход для пользователя.

  • Разрешение sessions:write. Только для решённых сессий (одобренных или отклонённых). До решения вызов возвращает 409.
  • Пустое тело сообщает confirmed_fraud с категорией other. status также может быть suspected_fraud или cleared, чтобы отозвать своё сообщение.
  • Категории: forged_document, impostor, deepfake_or_replay, synthetic_identity, account_takeover и other. С cleared категория не указывается.
  • В note можно написать свободный текст для наших аналитиков, не более 1000 символов. Никогда не указывайте в нём имена, номера документов или другие персональные данные.
  • Новое сообщение возвращает 201. Повторное такое же сообщение возвращает уже сохранённое с кодом 200. Передавайте Idempotency-Key, чтобы безопасно повторять запрос. Не более 20 сообщений на сессию.
  • То же сообщение можно отправить в один клик на странице сессии в панели.
POST /v1/partner/sessions/:id/fraud-reports
POST /v1/partner/sessions/ses_.../fraud-reports
Authorization: Bearer <partner key>
Content-Type: application/json
Idempotency-Key: chargeback-8812

{
  "status": "confirmed_fraud",
  "category": "impostor",
  "note": "Chargeback on the first deposit"
}

201 Created
{
  "id": "flb_...",
  "sessionId": "ses_...",
  "status": "confirmed_fraud",
  "category": "impostor",
  "noteKept": true,
  "createdAt": 1767225600000
}

Удаление

DELETE /v1/partner/users/:externalUserId удаляет данные пользователя, когда он закрывает аккаунт или просит удалить данные. Ответ показывает, что удалено сейчас, а что хранится, почему и до какого срока.

DELETE /v1/partner/users/:externalUserId
DELETE /v1/partner/users/user_42
Authorization: Bearer <partner key>

200 OK
{
  "deleted": ["images", "chip_files", "video", "personal_data", "address"],
  "retained": [
    { "category": "dedup_keys", "reason": "fraud_prevention", "until": 1924992000000 },
    { "category": "sanctions_screening_records", "reason": "legal_obligation", "until": 2082758400000 }
  ],
  "backupsPurgedWithinDays": 30
}

Ошибки

Все ошибки имеют одинаковый вид, и у каждого кода свой постоянный HTTP-статус.

error
{
  "error": {
    "code": "step_not_allowed",
    "message": "This step is not allowed now.",
    "requestId": "req_..."
  }
}
КодHTTPЗначение
bad_request400Запрос не соответствует контракту.
unauthorized401Ключ или токен отсутствует или неверен.
token_expired401Срок клиентского токена истёк. Создайте новый на бэкенде.
forbidden403Этому ключу это запрещено.
not_found404Такой сессии или пользователя нет.
session_closed409Сессия завершена. Загружать больше нельзя.
step_not_allowed409Неверный порядок, или шаг сейчас не нужен.
revision_conflict409Ревизия сессии изменилась. Прочитайте её заново.
idempotency_conflict409Тот же Idempotency-Key использован с другим запросом.
challenge_expired410Срок проверочного запроса истёк, или он уже использован. Запросите новый.
payload_too_large413Загрузка слишком большая.
unsupported_media_type415Этот тип файла не принимается.
rate_limited429Слишком много запросов. Повторите позже.
internal500Что-то пошло не так на нашей стороне.
dependency_unavailable503Нужный нам сервис недоступен. Повторите позже.

Коды причин

Сессии содержат reasonCodes, если нужна повторная отправка или в проверке отказано. Показывайте для каждого кода своё переведённое сообщение. Никогда не показывайте пользователям код как есть.

  • consent_missing
  • consent_withdrawn
  • attestation_failed
  • document_expired
  • document_unreadable
  • document_not_supported
  • document_suspected_tampering
  • document_number_visible
  • chip_verification_failed
  • chip_proof_missing
  • chip_data_mismatch
  • mrz_invalid
  • photo_quality
  • face_not_found
  • face_mismatch
  • liveness_failed
  • underage
  • location_not_supported
  • residence_not_supported
  • sanctions_match
  • pep_review
  • duplicate_identity
  • possible_duplicate
  • declaration_requires_review
  • user_requested_review
  • reviewer_other
  • address_unreadable
  • address_document_too_old
  • address_document_not_accepted
  • address_name_mismatch
  • address_signature_required
  • funds_document_unreadable
  • funds_document_too_old
  • funds_document_not_accepted
  • funds_more_evidence
  • funds_evidence_rejected