> ## Documentation Index
> Fetch the complete documentation index at: https://docs.didit.me/llms.txt
> Use this file to discover all available pages before exploring further.

# KYC API: полный сценарий интеграции Didit

> Интеграция Didit REST API от старта до решения KYC: получите API-ключ, создайте сессию, обработайте webhook и получите результат за минуты.

This guide walks you through the complete flow for integrating Didit identity verification via the REST API: getting credentials, creating a session, presenting it to the user, and receiving results.

***

## Шаг 1: Получение учётных данных

Есть два способа получить `api_key`:

### Вариант A: программная регистрация (без браузера)

Лучше всего подходит для CI/CD и ИИ-агентов. Два вызова API, `POST /programmatic/register/` и затем `POST /programmatic/verify-email/`, возвращают `api_key` прямо в ответе.

→ Полный процесс, правила пароля, срок действия JWT и политику блокировки см. в разделе [Программная регистрация](https://docs.didit.me/ru/integration/programmatic-registration.md).

### Вариант B: в консоли Didit

1. Перейдите в [Didit Console](https://business.didit.me).
2. Создайте организацию или выберите существующую.
3. Перейдите в **Settings** → **API & Webhooks**.
4. Скопируйте:
   * **API Key**: используется как заголовок `x-api-key` в каждом вызове верификации.
   * **Webhook Secret Key**: используется для проверки HMAC-подписей webhook.

### Переменные окружения

```bash theme={null}
# Required
DIDIT_API_KEY=your_api_key_here
DIDIT_WEBHOOK_SECRET=your_webhook_secret_here

# Your workflow ID (from the Console)
DIDIT_WORKFLOW_ID=your_workflow_id_here
```

***

## Шаг 2: Создание сценария

Перед созданием сессий нужен сценарий верификации. Сценарии определяют, через какие шаги верификации проходят пользователи.

### Создание в консоли

1. Перейдите в [Didit Console](https://business.didit.me) → **Workflows**
2. Нажмите **Create Workflow**
3. Выберите базовый шаблон:

| Шаблон | Для чего подходит |
| - | - |
| **KYC** | Полная верификация личности с документом |
| **Adaptive Age Verification** | Проверка возраста с необязательным резервным документом |
| **Биометрическая аутентификация** | Повторная верификация вернувшихся пользователей |
| **Address Verification** | Проверка подтверждения адреса |

4. Добавьте необязательные функции:

| Функция | Описание |
| - | - |
| NFC-верификация | Считывание данных чипа паспорта или удостоверения личности |
| Проверка живости | Защита от спуфинга с помощью видео селфи |
| Сравнение лиц | Сопоставление селфи с фото в документе |
| Проверка телефона | Проверка номера телефона по SMS |
| Проверка электронной почты | Проверка адреса электронной почты |
| AML-скрининг | Проверка по санкционным спискам и спискам PEP |
| Проверка по базам данных | Проверка по государственным базам данных |
| Анализ устройства и IP | Обнаружение VPN, прокси и геолокации |

5. Скопируйте **Workflow ID**

> [!NOTE]
>
> Подробнее о параметрах настройки см. в [документации по сценариям](https://docs.didit.me/ru/console/workflows.md).

***

## Шаг 3: Создание сессии верификации

Вызовите API, чтобы создать сессию для вашего пользователя.

### Запрос

```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "workflow_id": "your-workflow-id",
    "callback": "https://yourapp.com/verification-complete",
    "vendor_data": "user-123"
  }'
```

### Параметры запроса

| Параметр | Обязательный | Описание |
| - | - | - |
| `workflow_id` | Да | UUID вашего сценария из Console. |
| `callback` | Нет | URL, на который пользователь перенаправляется после верификации. Didit добавляет `verificationSessionId` и `status` как параметры запроса. Поддерживает пользовательские схемы (например, `myapp://`). Если параметр не указан, используется URL обратного вызова, настроенный для сценария. |
| `callback_method` | Нет | Какое устройство обрабатывает перенаправление: `initiator`, `completer` или `both`. По умолчанию: `initiator`. |
| `vendor_data` | Нет | Ваш внутренний идентификатор пользователя. Возвращается в webhook для сопоставления данных. |
| `metadata` | Нет | Произвольный JSON-объект, который сохраняется с сессией и возвращается в webhook. |
| `language` | Нет | Подсказка языка интерфейса (ISO 639-1, например `en`, `es`). Если параметр не указан, язык браузера определяется автоматически. |
| `contact_details` | Нет | Адрес электронной почты и телефон пользователя для предзаполнения и уведомлений по электронной почте. |
| `expected_details` | Нет | Ожидаемые данные пользователя для перекрестной проверки с верифицированными данными. |
| `portrait_image` | При определённых условиях | Изображение лица в кодировке Base64 (максимум 2 МБ; JPEG, PNG, WebP или TIFF). Эталонное лицо для сценариев с биометрической аутентификацией или сценариев, в которых сначала выполняется сравнение лиц. Необязателен, если у пользователя из `vendor_data` уже есть сохраненное лицо (одобренное при проверке живости изображение лица, фотография из ePassport, портрет из документа, удостоверяющего личность, или изображение лица из зарегистрированного профиля): Didit использует его автоматически. Если ни одного из них нет, возвращается `400`. |
| `sandbox_scenario` | Нет | Слаг сценария, доступный только в песочнице (например, `approve`, `decline_aml_hit`), который автоматически заполняет сессию тестовыми данными. |

### Полный пример со всеми параметрами

```bash theme={null}
curl -X POST https://verification.didit.me/v3/session/ \
  -H "Content-Type: application/json" \
  -H "x-api-key: YOUR_API_KEY" \
  -d '{
    "workflow_id": "11111111-2222-3333-4444-555555555555",
    "callback": "https://yourapp.com/verification-complete",
    "vendor_data": "user-123",
    "metadata": {
      "plan": "premium",
      "signup_source": "mobile-app"
    },
    "contact_details": {
      "email": "alex.sample@example.com",
      "email_lang": "en",
      "send_notification_emails": true,
      "phone": "+15550101000"
    },
    "expected_details": {
      "first_name": "John",
      "last_name": "Doe",
      "date_of_birth": "1990-01-01",
      "expected_document_types": ["P", "ID"]
    }
  }'
```

> [!NOTE]
>
> `expected_details.expected_document_types` ограничивает шаг верификации личности
> определенными типами документов: на экране выбора документа отображаются только
> запрошенные типы. Допустимые значения: `P` (паспорт), `ID` (национальное
> удостоверение личности), `DL` (водительское удостоверение), `RP` (вид на
> жительство), `HIC` (карта медицинского страхования), `TC` (налоговая карта),
> `SSC` (карта социального страхования). Значения не чувствительны к регистру и
> не повторяются; неизвестные значения возвращают `400`. Полную схему см. в
> [справочнике Create Session](https://docs.didit.me/ru/sessions-api/create-session.md).

### Ответ

`201 Created`:

```json theme={null}
{
  "session_id": "11111111-2222-3333-4444-555555555555",
  "session_number": 43762,
  "session_token": "3FaJ9wLqX2Mz",
  "url": "https://verify.didit.me/session/3FaJ9wLqX2Mz",
  "vendor_data": "user-123",
  "metadata": {
    "plan": "premium",
    "signup_source": "mobile-app"
  },
  "status": "Not Started",
  "workflow_id": "11111111-2222-3333-4444-555555555555",
  "workflow_version": 3,
  "callback": "https://yourapp.com/verification-complete"
}
```

### Ключевые поля ответа

| Поле | Описание |
| - | - |
| `session_id` | Уникальный UUID этой сессии. Используйте его для `GET /v3/session/{sessionId}/decision/` и webhook. |
| `session_number` | Удобочитаемый порядковый номер, который отображается в консоли Didit. |
| `session_token` | URL-безопасный токен из 12 символов, который авторизует конечного пользователя для открытия размещённого процесса верификации и инициализирует нативные SDK (веб-SDK вместо этого принимает `url`). Храните его в секрете. |
| `url` | URL размещенной страницы верификации, на который перенаправляется пользователь или который встраивается в iframe. (Имя поля: `url`, а не `verification_url`.) Если в запросе передан `language`, URL содержит сегмент пути с языком, например `https://verify.didit.me/en/session/3FaJ9wLqX2Mz`. |
| `status` | Текущий статус. Для только что созданной сессии это `"Not Started"`. |
| `workflow_id` | Стабильный идентификатор сценария, в рамках которого выполняется сессия. |
| `workflow_version` | Опубликованная версия сценария, которая была закреплена за сессией при создании. |

> [!NOTE]
>
> **Идемпотентность.** Если передан `vendor_data` и на последней опубликованной
> версии сценария уже существует незавершенная сессия (`Not Started`, `In Progress`,
> `Resubmitted` или `Awaiting User`) с тем же `vendor_data`, возвращается эта
> существующая сессия (все так же с кодом `201`), а не создается дубликат. При
> этом ее `callback` и `metadata` обновляются новыми значениями, а `status`
> отражает статус существующей сессии.

> [!NOTE]
>
> **Отсутствие `x-api-key` возвращает `403`** с телом
> `{"detail": "You do not have permission to perform this action."}`, таким же,
> как у действительного ключа без разрешения. Неверный, отозванный или
> истекший ключ возвращает `401`. Недостаточно кредитов: возвращается `400`.
> Полную документацию см. в [справочнике API Create Session](https://docs.didit.me/ru/sessions-api/create-session.md).

***

## Шаг 4: Проведение верификации для пользователя

Выберите, как показать пользователю процесс верификации:

### Вариант A: нативный SDK (рекомендуется для мобильных приложений)

Используйте `session_token` с нативными SDK для интеграции камеры, NFC и биометрии.

→ [iOS SDK](https://docs.didit.me/ru/integration/native-sdks/ios-sdk.md) · [Android SDK](https://docs.didit.me/ru/integration/native-sdks/android-sdk.md) · [React Native](https://docs.didit.me/ru/integration/native-sdks/react-native-sdk.md) · [Flutter](https://docs.didit.me/ru/integration/native-sdks/flutter-sdk.md)

### Вариант B: встраиваемый iframe InContext (рекомендуется для веб-сайтов)

Встройте верификацию на свою страницу, используя `url`, полученный на шаге 3.

```html theme={null}
<iframe
  src="{url}"
  style="width: 100%; height: 700px; border: none;"
  allow="camera; microphone; geolocation; clipboard-write; fullscreen; autoplay; encrypted-media; digital-credentials-get"
></iframe>
```

→ [iframe InContext](https://docs.didit.me/ru/integration/web-sdks/incontext-iframe.md)

### Вариант C: перенаправление

Перенаправьте пользователя на URL верификации.

```javascript theme={null}
window.location.href = session.url;
```

→ [Веб-перенаправление](https://docs.didit.me/ru/integration/web-sdks/web-redirect.md)

### Вариант D: мобильный WebView

Для мобильных приложений без нативного SDK.

```jsx theme={null}
// React Native
<WebView source={{ uri: session.url }} />
```

→ [WebView в iOS и Android](https://docs.didit.me/ru/integration/web-sdks/webview-in-ios-android.md)

***

## Шаг 5: Получение результатов

### Через webhook (рекомендуется)

Webhook уведомляет ваш сервер в реальном времени при изменении статуса верификации.

**Настройка:**

1. Создайте адрес назначения webhook в [Консоли](https://business.didit.me) (**API и вебхуки**) или через [`POST /v3/webhook/destinations/`](https://docs.didit.me/ru/management-api/webhook/create-destination) с `webhook_version: "v3"` и массивом `subscribed_events` (например, `["status.updated"]`)
2. Сохраните `secret_shared_key`, который возвращается при создании: это секрет HMAC для всех вариантов подписи
3. Реализуйте на своем сервере endpoint webhook

**Пример payload webhook:**

Внутри `decision` каждый результат по отдельной функции передается как **массив во множественном числе** (`id_verifications[]`, `nfc_verifications[]`, `liveness_checks[]`, `face_matches[]`, …): одна запись на каждый узел сценария. Это контракт V3, и его нельзя менять: никогда не пишите код, опирающийся на ключи `nfc`, `id_verification` или `liveness` в единственном числе.

```json theme={null}
{
  "event_id": "9c0c8b8a-1111-4222-9333-444444444444",
  "webhook_type": "status.updated",
  "timestamp": 1774970000,
  "created_at": 1774969994,
  "application_id": "11111111-2222-3333-4444-555555555555",
  "environment": "live",
  "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
  "status": "Approved",
  "workflow_id": "22222222-3333-4444-5555-666666666666",
  "workflow_version": 4,
  "vendor_data": "user-123",
  "metadata": { "plan": "premium" },
  "decision": {
    "session_id": "aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee",
    "status": "Approved",
    "id_verifications": [
      { "node_id": "id_verification_1", "status": "Approved", "document_type": "Passport", "warnings": [] }
    ],
    "liveness_checks": [
      { "node_id": "liveness_1", "status": "Approved", "method": "PASSIVE", "score": 95.4, "warnings": [] }
    ],
    "face_matches": [
      { "node_id": "face_match_1", "status": "Approved", "score": 96.1, "warnings": [] }
    ],
    "reviews": []
  }
}
```

Объект `decision` присутствует только тогда, когда `status` имеет значение `Approved`, `Declined`, `In Review` или `Abandoned` (событие `Resubmitted` содержит `resubmit_info`). Didit использует один и тот же `event_id` при повторных отправках и при отправке на несколько адресов назначения, поэтому по нему нужно строить идемпотентность. Все массивы во множественном числе и поля описаны в разделе [Модели данных](https://docs.didit.me/ru/reference/data-models.md).

**Проверка подписи (X-Signature-V2, рекомендуется):**

`X-Signature-V2` — это HMAC-SHA256, вычисляемый по канонической перекодировке JSON-тела: значения с плавающей точкой, представляющие целые числа, сокращаются до целых чисел, ключи рекурсивно сортируются, используются компактные разделители, Unicode сохраняется.

```javascript theme={null}
const crypto = require("crypto");

// Match Didit's float normalisation: whole-valued floats serialise as ints.
function shortenFloats(data) {
  if (Array.isArray(data)) return data.map(shortenFloats);
  if (data !== null && typeof data === "object") {
    return Object.fromEntries(
      Object.entries(data).map(([key, value]) => [key, shortenFloats(value)])
    );
  }
  return data;
}

// Sort object keys recursively before re-stringifying.
function sortKeys(obj) {
  if (Array.isArray(obj)) return obj.map(sortKeys);
  if (obj !== null && typeof obj === "object") {
    return Object.keys(obj).sort().reduce((acc, key) => {
      acc[key] = sortKeys(obj[key]);
      return acc;
    }, {});
  }
  return obj;
}

function verifyWebhook(rawBody, signatureV2, timestamp, secret) {
  // 1. Reject anything older than 5 minutes
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp, 10)) > 300) return false;

  // 2. Recompute HMAC-SHA256 against the canonical JSON Didit signs
  const canonical = JSON.stringify(sortKeys(shortenFloats(JSON.parse(rawBody))));
  const expected = crypto.createHmac("sha256", secret).update(canonical, "utf8").digest("hex");

  // 3. Constant-time compare
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(signatureV2, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

→ [Полная документация по webhook](https://docs.didit.me/ru/integration/webhooks.md), включая `X-Signature` по исходным байтам, устаревший `X-Signature-Simple` с подписью только конверта и рекомендации по выбору варианта. Didit повторяет неудачные доставки (`5xx`, `404`, тайм-аут) до 2 раз (через \~1 мин, затем через \~4 мин) и прерывает ожидание через **5 секунд**, поэтому возвращайте `2xx` быстро.

### Через API (необязательно)

Получить тот же payload решения можно в любой момент:

```bash theme={null}
curl https://verification.didit.me/v3/session/{session_id}/decision/ \
  -H "x-api-key: YOUR_API_KEY"
```

Структура ответа (массивы во множественном числе) совпадает с объектом `decision` в payload webhook, а endpoint можно вызвать на любом этапе жизненного цикла сессии. Используйте webhook для обновлений в реальном времени, а вызовы API оставьте для догрузки данных, аудита и восстановления: endpoint `/decision/` ограничен 600 GET-запросами в минуту на один API-ключ (см. [Ограничение частоты запросов](https://docs.didit.me/ru/integration/rate-limiting.md)). Семантика ошибок: неизвестный `session_id` возвращает `404` с `{"detail": "Not found."}`; ошибки аутентификации возвращаются как `403`, этот endpoint никогда не возвращает `401`. Ссылки на медиафайлы в ответе — это предварительно подписанные ссылки с коротким сроком действия, поэтому загружайте их сразу и не сохраняйте.

→ [Справочник API для получения сессии](https://docs.didit.me/ru/sessions-api/retrieve-session.md)

***

## Полные примеры кода

### Node.js / Express

```javascript theme={null}
const express = require("express");
const crypto = require("crypto");
const app = express();

// Create verification session
app.post("/api/verify", async (req, res) => {
  const { userId } = req.body;

  const response = await fetch("https://verification.didit.me/v3/session/", {
    method: "POST",
    headers: {
      "x-api-key": process.env.DIDIT_API_KEY,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      workflow_id: process.env.DIDIT_WORKFLOW_ID,
      callback: `${process.env.APP_URL}/verification-complete`,
      vendor_data: userId,
    }),
  });

  const session = await response.json();

  // Store session_id associated with user
  await db.users.update({
    where: { id: userId },
    data: { verificationSessionId: session.session_id },
  });

  res.json({
    verificationUrl: session.url,             // hosted verification URL
    sessionToken: session.session_token,      // for native/web SDKs
  });
});

// Handle webhook — uses verifyWebhook / shortenFloats / sortKeys from Step 5
app.post(
  "/api/webhook",
  express.raw({ type: "application/json" }),
  (req, res) => {
    const signature = req.get("X-Signature-V2");
    const timestamp = req.get("X-Timestamp");

    if (
      !verifyWebhook(
        req.body.toString("utf8"),
        signature,
        timestamp,
        process.env.DIDIT_WEBHOOK_SECRET,
      )
    ) {
      return res.status(401).json({ error: "Invalid signature" });
    }

    const payload = JSON.parse(req.body);
    const { event_id, session_id, status, vendor_data } = payload;

    // Idempotency: the same event_id is reused on retries and fan-out.
    // Skip events you have already processed (e.g. a processed_events table).

    // Update user's verification status (status values are case-sensitive strings)
    if (status === "Approved") {
      db.users.update({
        where: { id: vendor_data },
        data: { isVerified: true, verifiedAt: new Date() },
      });
    }

    // Return 2xx quickly (Didit's delivery timeout is 5s); do heavy work asynchronously
    res.json({ received: true });
  },
);

app.listen(3000);
```

### Python / FastAPI

```python theme={null}
from fastapi import FastAPI, Request, HTTPException
import httpx
import hashlib
import hmac
import json
import os
import time

app = FastAPI()

@app.post("/api/verify")
async def create_verification(user_id: str):
    async with httpx.AsyncClient() as client:
        response = await client.post(
            "https://verification.didit.me/v3/session/",
            headers={
                "x-api-key": os.environ["DIDIT_API_KEY"],
                "Content-Type": "application/json"
            },
            json={
                "workflow_id": os.environ["DIDIT_WORKFLOW_ID"],
                "callback": f"{os.environ['APP_URL']}/verification-complete",
                "vendor_data": user_id
            }
        )

    session = response.json()
    return {
        "verification_url": session["url"],            # hosted verification URL
        "session_token": session["session_token"],     # for native/web SDKs
    }

def shorten_floats(data):
    """Match Didit: whole-valued floats serialise as ints."""
    if isinstance(data, dict):
        return {k: shorten_floats(v) for k, v in data.items()}
    if isinstance(data, list):
        return [shorten_floats(x) for x in data]
    if isinstance(data, float) and data.is_integer():
        return int(data)
    return data

@app.post("/api/webhook")
async def handle_webhook(request: Request):
    payload = await request.json()
    signature = request.headers.get("X-Signature-V2")
    timestamp = request.headers.get("X-Timestamp")

    # Verify timestamp freshness
    if not signature or not timestamp or abs(int(time.time()) - int(timestamp)) > 300:
        raise HTTPException(status_code=401, detail="Request too old")

    # Recompute Didit's V2 canonical JSON: shortened floats, sorted keys,
    # compact separators, Unicode preserved (ensure_ascii=False)
    canonical = json.dumps(
        shorten_floats(payload),
        sort_keys=True,
        separators=(",", ":"),
        ensure_ascii=False,
    )
    expected = hmac.new(
        os.environ["DIDIT_WEBHOOK_SECRET"].encode("utf-8"),
        canonical.encode("utf-8"),
        hashlib.sha256
    ).hexdigest()

    if not hmac.compare_digest(signature, expected):
        raise HTTPException(status_code=401, detail="Invalid signature")

    # Idempotency: dedupe on payload["event_id"] (reused on retries)

    if payload["status"] == "Approved":
        # Update user in database
        pass

    return {"received": True}
```

***

## Статусы сессии

Ниже перечислены 10 возможных значений `status`. Это точные строки с учетом регистра (обратите внимание: `Kyc Expired` записывается с одной заглавной буквой `K`):

| Статус | Описание |
| - | - |
| `Not Started` | Сессия создана, но пользователь еще не открыл ссылку. |
| `In Progress` | Пользователь сейчас проходит верификацию. |
| `In Review` | Верификация отмечена для проверки человеком. |
| `Approved` | Все проверки пройдены. |
| `Declined` | Одна или несколько проверок не пройдены. |
| `Resubmitted` | Проверяющий запросил повторное выполнение определенных шагов. |
| `Awaiting User` | Родительская сессия KYB ожидает завершения дочерних участников KYC. |
| `Abandoned` | Пользователь начал, но не завершил верификацию за отведенное время. |
| `Expired` | Сессия истекла до того, как пользователь открыл ссылку. |
| `Kyc Expired` | Ранее одобренная сессия, срок действия документа которой истек. |

Полный жизненный цикл, переходы между статусами и диаграмму Mermaid см. в разделе [Статусы верификации](https://docs.didit.me/ru/integration/verification-statuses.md).

***

## Следующие шаги

1. **Выберите способ интеграции**:
   * Мобильные приложения → [Нативные SDK](https://docs.didit.me/ru/integration/native-sdks/overview.md)
   * Веб-приложения → [Веб-SDK](https://docs.didit.me/ru/integration/web-sdks/overview.md)
2. **Настройте webhook**: [Webhook](https://docs.didit.me/ru/integration/webhooks.md)
3. **Спланируйте необходимые мощности**: [Ограничение частоты запросов](https://docs.didit.me/ru/integration/rate-limiting.md)
4. **Обработайте каждый статус**: [Статусы верификации](https://docs.didit.me/ru/integration/verification-statuses.md)
5. **Настройте сценарий**: [Сценарии](https://docs.didit.me/ru/console/workflows.md)
6. **Проверьте доступность API**: [Проверка состояния](https://docs.didit.me/ru/api-reference/healthcheck.md)

## Нужна помощь?

* **Поддержка**: [support@didit.me](mailto:support@didit.me)
* **Программный онбординг**: [Программная регистрация](https://docs.didit.me/ru/integration/programmatic-registration.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.