К содержимому
OneKYC
Tenant API (KYC)

Вебхуки

Обновлено:

Система вебхуков OneKYC обеспечивает доставку уведомлений о событиях верификации на указанный URL-адрес в режиме реального времени. Поддерживаются события KYC (сессии, верификация, шаги, документы, ссылки и др.) и KYB (верификация юридических лиц). Выбор типов событий осуществляется при настройке endpoint в панели администратора.

Основные характеристики:

  • События KYC (сессии, верификация, шаги, документы, ссылки и др.) и KYB (см. Вебхуки KYB)
  • Криптографическая подпись HMAC-SHA256 для обеспечения безопасности доставки
  • Автоматические повторные попытки с экспоненциальным увеличением интервала (до 5 попыток)
  • Ключи идемпотентности на основе SHA-256 для предотвращения дублирования
  • Журнал доставки и метрики в панели администратора

Настройка выполняется в панели администратора: вы указываете URL-адрес вашего сервера, выбираете типы событий (или все типы) и сохраняете конфигурацию. Система OneKYC направляет POST-запросы с криптографической подписью при каждом событии.

Настройка

В разделе Вебхуки панели администратора добавьте URL-адрес вашего сервера, выберите необходимые типы событий (или все типы) и сохраните настройки. При создании endpoint система генерирует секретный ключ (с префиксом whsec_) для проверки подписи. Секретный ключ отображается однократно — сохраните его в надёжном месте.

Типы событий

События KYC сгруппированы по категориям, перечисленным ниже. События KYB (категория KYB events в панели администратора) описаны в разделе Вебхуки KYB.

Сессии (5 событий)

СобытиеОписание
session.createdСоздана новая сессия верификации
session.startedСессия запущена (пользователь начал прохождение)
session.completedСессия завершена — терминальное событие, отправляется только после финального решения (автоматического или оператора ручного ревью)
session.expiredСрок действия сессии истёк
session.cancelledСессия отменена

Верификация (6 событий)

СобытиеОписание
verification.approvedВерификация одобрена
verification.declinedВерификация отклонена
verification.needs_reviewВерификация направлена на ручную проверку оператором (не терминальное состояние)
verification.ocr.correctedДанные OCR исправлены оператором
verification.resubmit_requestedОператор запросил повторную загрузку шага (Manual Review Resubmit)
verification.resubmit_fulfilledЗаявитель повторно загрузил запрошенный шаг
verification.enrichment.updatedОбновлена безопасная сводка асинхронного enrichment: покрытие и решение

Порядок событий для сессий с ручным ревью

Если сессия требует ручной проверки оператором, порядок отправки событий следующий:

  1. verification.needs_review — сразу после того, как автоматическая проверка завершилась с решением "нужна ручная проверка", или когда оператор вручную возвращает терминальную сессию на ревью. session.completed не отправляется на этом этапе — сессия ещё не финализирована.
  2. verification.approved или verification.declined — после финального решения оператора.
  3. session.completed — сразу после события verification.approved / verification.declined. Отправляется одной цепочкой событий.

Если вы подписаны только на session.completed, вы получите это событие ровно один раз — в момент финализации сессии. Промежуточное состояние "на ручной проверке" вы не увидите; для этого подпишитесь на verification.needs_review.

Для сессий без ручной проверки (автоматическое одобрение/отклонение) порядок событий: verification.approved / verification.declined → session.completed.

session.completed является финальным событием для клиентского webhook-цикла. Внутри продукта оператор может позже вернуть завершённую сессию на ревью или запросить пересдачу шага; такие действия отправляют verification.needs_review или события resubmit и не переигрывают исходный session.completed до нового финального решения.

Шаги (5 событий)

СобытиеОписание
step.startedШаг верификации начат
step.completedШаг верификации завершён
step.failedШаг завершился с ошибкой
step.skippedШаг пропущен, потому что вышестоящая зависимость не прошла
step.retriedПовторное выполнение шага

Документы (1 событие)

СобытиеОписание
document.uploadedДокумент загружен

AML-скрининг (3 события)

СобытиеОписание
aml.screening_completedAML-проверка завершена
aml.hit_foundОбнаружено совпадение в базах AML
aml.clearedAML-проверка завершена без совпадений

Ссылки верификации (4 события)

СобытиеОписание
link.createdСсылка верификации создана
link.consumedСсылка использована
link.expiredСрок действия ссылки истёк
link.revokedСсылка отозвана

Формат запроса и подпись

OneKYC направляет на указанный URL-адрес POST-запрос с телом в формате JSON. Каждый запрос содержит следующие заголовки:

ЗаголовокОписание
Content-Typeapplication/json
X-Onekyc-SignatureПодпись в формате t=<timestamp>,v1=<signature>
X-Onekyc-Event-TypeТип события (например, session.created)
X-Onekyc-Event-IdUUID события
X-Onekyc-TimestampUnix-время отправки
User-AgentOneKYC-Webhook/1.0

Имена HTTP-заголовков нечувствительны к регистру (RFC 7230 §3.2). На стороне приёмника используйте сравнение без учёта регистра (большинство HTTP-фреймворков делает это автоматически).

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

Для верификации подлинности вебхука выполните следующие шаги:

  1. Извлеките значения t (timestamp) и v1 (signature) из заголовка X-Onekyc-Signature.
  2. Сформируйте строку для подписи: {timestamp}.{raw_body} (числовое значение из t, символ точки, затем тело запроса в исходном виде).
  3. Вычислите HMAC-SHA256 с использованием вашего секретного ключа вебхука (whsec_...).
  4. Сравните полученное значение с v1 из заголовка (используйте сравнение с постоянным временем выполнения — constant-time comparison).
  5. Убедитесь, что значение timestamp не старше допустимого временного окна (рекомендуемое значение — 5 минут).

Защита от replay-атак: отклоняйте запросы, у которых |текущее_время - X-Onekyc-Timestamp| > 300 секунд (5 минут). Дополнительно используйте заголовок X-Onekyc-Event-Id (UUID) для дедупликации — сохраняйте обработанные event ID и игнорируйте повторные доставки.

При ротации секретного ключа в заголовке дополнительно присутствует v2=<signature> — подпись новым ключом. Оба ключа действуют одновременно в течение периода грации.

Примеры кода для проверки подписи

Node.js:

const crypto = require('crypto');

function verifyWebhookSignature(rawBody, signature, secret) {
  // Извлечение timestamp и signature из заголовка
  const parts = signature.split(',');
  const timestamp = parts.find(p => p.startsWith('t=')).split('=')[1];
  const v1Signature = parts.find(p => p.startsWith('v1=')).split('=')[1];

  // Проверка актуальности timestamp (не старше 5 минут)
  const maxAgeSeconds = 300; // 5 минут
  const currentTime = Math.floor(Date.now() / 1000);
  if (currentTime - parseInt(timestamp) > maxAgeSeconds) {
    throw new Error('Webhook timestamp is too old');
  }

  // Формирование строки для подписи: timestamp.raw_body
  const signedPayload = `${timestamp}.${rawBody}`;

  // Вычисление HMAC-SHA256
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');

  // Сравнение с постоянным временем выполнения
  if (!crypto.timingSafeEqual(
    Buffer.from(expectedSignature),
    Buffer.from(v1Signature)
  )) {
    throw new Error('Invalid webhook signature');
  }

  return true;
}

Go:

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

func verifyWebhookSignature(rawBody []byte, signature, secret string) error {
    // Извлечение timestamp и signature из заголовка
    parts := strings.Split(signature, ",")
    var timestamp, v1Signature string

    for _, part := range parts {
        if strings.HasPrefix(part, "t=") {
            timestamp = strings.TrimPrefix(part, "t=")
        } else if strings.HasPrefix(part, "v1=") {
            v1Signature = strings.TrimPrefix(part, "v1=")
        }
    }

    if timestamp == "" || v1Signature == "" {
        return errors.New("invalid signature format")
    }

    // Проверка актуальности timestamp (не старше 5 минут)
    ts, err := strconv.ParseInt(timestamp, 10, 64)
    if err != nil {
        return fmt.Errorf("invalid timestamp: %w", err)
    }

    maxAge := int64(300) // 5 минут
    if time.Now().Unix()-ts > maxAge {
        return errors.New("webhook timestamp is too old")
    }

    // Формирование строки для подписи: timestamp.raw_body
    signedPayload := fmt.Sprintf("%s.%s", timestamp, rawBody)

    // Вычисление HMAC-SHA256
    mac := hmac.New(sha256.New, []byte(secret))
    mac.Write([]byte(signedPayload))
    expectedSignature := hex.EncodeToString(mac.Sum(nil))

    // Сравнение с постоянным временем выполнения
    if subtle.ConstantTimeCompare(
        []byte(expectedSignature),
        []byte(v1Signature),
    ) != 1 {
        return errors.New("invalid webhook signature")
    }

    return nil
}

Формат payload

Все события передаются в едином формате конверта:

{
  "id": "evt_uuid",
  "type": "session.created",
  "api_version": "2026-02-01",
  "created_at": "2026-02-01T12:00:00Z",
  "tenant_id": "uuid",
  "environment": "production",
  "idempotency_key": "unique_key",
  "data": { ... }
}

Содержимое поля data определяется типом события (сессия, верификация, шаг, документ, ссылка и др.).

Политика повторных попыток

При ошибке доставки (ответ с кодом, отличным от 2xx) система выполняет до 5 общих попыток доставки с экспоненциальным увеличением интервала (базовая задержка — 5 секунд, удваивается на каждой повторной попытке, максимальный интервал — 24 часа, добавляется случайный jitter ±20% для размазывания нагрузки). После исчерпания всех попыток событие помещается в очередь недоставленных сообщений (Dead Letter Queue).

Для подтверждения успешного получения вебхука ваш сервер должен вернуть ответ с HTTP-кодом 2xx.

Ключ идемпотентности (idempotency_key): генерируется на основе SHA-256 хеша логической идентичности события и предназначен для дедупликации событий на стороне получателя. Значение представляет собой 32-символьную шестнадцатеричную строку.

Пример: a1b2c3d4e5f6a1b2c3d4e5f6a1b2

Некоторые payload финальных решений по сессии могут содержать дополнительные поля, если соответствующие данные доступны: applicant.details, applicant.localized, media_files, decision_reasons и custom_steps. Эти поля добавочные; получатель вебхука должен игнорировать поля, которые не использует.

Примеры payload для событий

События сессий (session.*)

Все события сессий содержат плоскую структуру данных с информацией о сессии. Терминальные KYC-события, такие как session.completed, также включают обогащённые блоки applicant и verifications. Если во flow есть шаги подтверждения телефона или email по OTP, подтверждённые значения передаются как data.applicant.phone и data.applicant.email.

Поле верхнего уровня result является источником истины для финального решения по сессии. Массив verifications может включать исторические повторные попытки одной проверки. Каждый элемент проверки включает attempt_number и is_current, когда эти данные доступны; используйте is_current: true как актуальный результат конкретной проверки. Например, если живость документа завалилась на 1-й попытке и прошла на 2-й, терминальный approved-payload может содержать обе строки, но только строка 2-й попытки будет помечена как текущая.

{
  "id": "evt_550e8400-e29b-41d4-a716-446655440000",
  "type": "session.completed",
  "api_version": "2026-02-01",
  "created_at": "2026-02-11T12:00:00Z",
  "tenant_id": "550e8400-e29b-41d4-a716-446655440001",
  "environment": "production",
  "idempotency_key": "example-idempotency-key",
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "flow_id": "550e8400-e29b-41d4-a716-446655440003",
    "flow_version": 1,
    "external_id": "user_12345",
    "status": "completed",
    "created_at": "2026-02-11T10:00:00Z",
    "started_at": "2026-02-11T10:05:00Z",
    "completed_at": "2026-02-11T12:00:00Z",
    "expires_at": "2026-02-12T10:00:00Z",
    "steps_completed": 5,
    "result": "approved",
    "applicant": {
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "JOHN DOE",
      "document_number": "AB1234567",
      "document_type": "passport",
      "date_of_birth": "1990-01-15",
      "phone": "+12125550100",
      "email": "john.doe@example.com"
    },
    "verifications": [
      {
        "type": "document_ocr",
        "step_id": "550e8400-e29b-41d4-a716-446655440010",
        "attempt_number": 1,
        "is_current": true,
        "status": "passed",
        "score": 0.95,
        "ocr": {
          "document_number": "AB1234567",
          "full_name": "JOHN DOE",
          "first_name": "JOHN",
          "last_name": "DOE",
          "date_of_birth": "1990-01-15",
          "issue_date": "2020-01-15",
          "expiry_date": "2030-01-15",
          "issuing_country": "USA",
          "document_type": "passport",
          "localized": {
            "English": {
              "Full Name": "JOHN DOE"
            }
          }
        }
      },
      {
        "type": "document_liveness",
        "step_id": "550e8400-e29b-41d4-a716-446655440010",
        "attempt_number": 2,
        "is_current": true,
        "status": "passed",
        "score": 0.96,
        "doc_liveness": {
          "is_real": true,
          "score": 0.96
        }
      }
    ]
  }
}

Полная схема полей сессии доступна в Справочнике API (Swagger / OpenAPI).

События верификации (verification.*)

События верификации содержат полную информацию о сессии, извлечённые данные заявителя из документа и результаты всех выполненных проверок.

verification.approved:

{
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "flow_id": "550e8400-e29b-41d4-a716-446655440003",
    "flow_version": 1,
    "external_id": "user_12345",
    "status": "completed",
    "result": "approved",
    "created_at": "2026-02-11T10:00:00Z",
    "started_at": "2026-02-11T10:01:00Z",
    "completed_at": "2026-02-11T12:00:00Z",
    "expires_at": "2026-02-12T10:00:00Z",
    "steps_completed": 5,
    "applicant": {
      "first_name": "John",
      "last_name": "Doe",
      "full_name": "John Doe",
      "middle_name": "Michael",
      "gender": "M",
      "nationality": "USA",
      "issuing_country": "USA",
      "document_number": "AB1234567",
      "document_type": "passport",
      "date_of_birth": "1990-01-15",
      "date_of_expiry": "2030-01-15",
      "date_of_issue": "2020-01-15",
      "phone": "+12125550100",
      "email": "john.doe@example.com"
    },
    "verifications": [
      {
        "type": "document_ocr",
        "step_id": "550e8400-e29b-41d4-a716-446655440010",
        "attempt_number": 1,
        "is_current": true,
        "status": "passed",
        "score": 0.95,
        "ocr": {
          "document_number": "AB1234567",
          "full_name": "JOHN DOE",
          "first_name": "JOHN",
          "last_name": "DOE",
          "date_of_birth": "1990-01-15",
          "nationality": "USA",
          "gender": "M",
          "issue_date": "2020-01-15",
          "expiry_date": "2030-01-15",
          "issuing_country": "USA",
          "issuing_authority": "U.S. Department of State",
          "document_type": "passport",
          "mrz_type": "TD3"
        }
      },
      {
        "type": "document_liveness",
        "step_id": "550e8400-e29b-41d4-a716-446655440010",
        "attempt_number": 2,
        "is_current": true,
        "status": "passed",
        "score": 0.96,
        "doc_liveness": {
          "is_real": true,
          "score": 0.96,
          "status": "Ok"
        }
      },
      {
        "type": "face_liveness",
        "step_id": "550e8400-e29b-41d4-a716-446655440011",
        "attempt_number": 2,
        "is_current": true,
        "status": "passed",
        "score": 0.97,
        "face_liveness": {
          "is_live": true,
          "score": 0.97,
          "faces": [
            {
              "x1": 80.0, "y1": 60.0, "x2": 220.0, "y2": 260.0,
              "yaw": 2.3, "pitch": -1.1, "roll": 0.5,
              "left_eye_closed": 0.03, "right_eye_closed": 0.04,
              "state": { "is_front": true, "quality": "High", "luminance": "Normal", "result": "Real" }
            }
          ]
        }
      },
      {
        "type": "face_match",
        "status": "passed",
        "score": 0.94,
        "face_match": {
          "score": 0.94,
          "is_match": true,
          "threshold": 0.7,
          "faces1": [
            {
              "x1": 80.0, "y1": 60.0, "x2": 220.0, "y2": 260.0,
              "yaw": 2.3, "pitch": -1.1, "roll": 0.5,
              "left_eye_closed": 0.03, "right_eye_closed": 0.04,
              "state": { "is_front": true, "quality": "High", "luminance": "Normal", "result": "Real" }
            }
          ],
          "faces2": [
            {
              "x1": 80.0, "y1": 60.0, "x2": 220.0, "y2": 260.0,
              "yaw": 2.3, "pitch": -1.1, "roll": 0.5,
              "left_eye_closed": 0.03, "right_eye_closed": 0.04,
              "state": { "is_front": true, "quality": "High", "luminance": "Normal", "result": "Real" }
            }
          ]
        }
      },
      {
        "type": "aml_screening",
        "status": "passed",
        "score": 0.9
      },
      {
        "type": "proof_of_address",
        "status": "passed",
        "score": 0.93,
        "proof_of_address": {
          "decision": "approved",
          "route": "gps_document",
          "reason_codes": ["gps_country_match", "document_address_match"],
          "applicant_message_code": "poa_approved",
          "reviewer_summary": "GPS, IP, and document evidence match the configured address policy.",
          "address_country": "USA",
          "address_city": "New York",
          "address_postal_code": "10001",
          "gps_accuracy_meters": 18.5,
          "ip_country": "USA",
          "ip_expected": "USA",
          "fingerprint_state": "consistent",
          "document_available": true,
          "document_category": "utility",
          "document_subtype": "utility_bill",
          "document_country": "USA"
        }
      }
    ]
  }
}

verification.declined:

{
  "data": {
    "session_id": "...",
    "result": "declined",
    "applicant": { "first_name": "Jane", "last_name": "Doe", "document_number": "CD9876543", "document_type": "passport" },
    "verifications": [
      {
        "type": "document_ocr",
        "status": "failed",
        "score": 0.95,
        "rejection_reasons": ["document_expired"],
        "ocr": {
          "document_number": "CD9876543",
          "first_name": "JANE",
          "last_name": "DOE",
          "date_of_birth": "1990-05-15",
          "expiry_date": "2020-01-01",
          "issuing_country": "USA",
          "document_type": "passport"
        }
      }
    ]
  }
}

verification.needs_review:

{
  "data": {
    "session_id": "...",
    "result": "review",
    "applicant": { "first_name": "Alice", "last_name": "Smith", "document_number": "EF5551234", "document_type": "passport" },
    "verifications": [
      {
        "type": "document_ocr",
        "status": "passed",
        "score": 0.91,
        "ocr": {
          "document_number": "EF5551234",
          "first_name": "ALICE",
          "last_name": "SMITH",
          "date_of_birth": "1985-03-20",
          "expiry_date": "2030-03-19",
          "issuing_country": "USA",
          "document_type": "passport"
        }
      },
      {
        "type": "face_match",
        "status": "review",
        "score": 0.76,
        "face_match": {
          "score": 0.76,
          "is_match": false,
          "threshold": 0.7,
          "faces1": [
            {
              "x1": 80.0, "y1": 60.0, "x2": 220.0, "y2": 260.0,
              "yaw": 2.3, "pitch": -1.1, "roll": 0.5,
              "left_eye_closed": 0.03, "right_eye_closed": 0.04,
              "state": { "is_front": true, "quality": "High", "luminance": "Normal", "result": "Real" }
            }
          ],
          "faces2": [
            {
              "x1": 80.0, "y1": 60.0, "x2": 220.0, "y2": 260.0,
              "yaw": 2.3, "pitch": -1.1, "roll": 0.5,
              "left_eye_closed": 0.03, "right_eye_closed": 0.04,
              "state": { "is_front": true, "quality": "High", "luminance": "Normal", "result": "Real" }
            }
          ]
        }
      },
      {
        "type": "proof_of_address",
        "status": "review",
        "score": 0.62,
        "proof_of_address": {
          "decision": "needs_review",
          "route": "document",
          "reason_codes": ["document_review_required"],
          "applicant_message_code": "poa_review_required",
          "reviewer_summary": "Document address was extracted but needs operator confirmation.",
          "address_country": "USA",
          "address_city": "Austin",
          "address_postal_code": "78701",
          "ip_country": "USA",
          "document_available": true,
          "document_category": "bank",
          "document_subtype": "bank_statement",
          "document_ocr_confidence_bucket": "medium",
          "document_ocr_degraded": false,
          "document_country": "USA"
        }
      }
    ]
  }
}

Полная схема всех полей с типами, форматами и описаниями доступна в Справочнике API (Swagger / OpenAPI).

События шагов (step.*)

События шагов содержат информацию о выполнении конкретного шага верификации:

{
  "type": "step.completed",
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "step_id": "550e8400-e29b-41d4-a716-446655440010",
    "step_key": "document_capture",
    "step_type": "document",
    "step_name": "Upload Document",
    "execution_id": "550e8400-e29b-41d4-a716-446655440020",
    "attempt_number": 1,
    "status": "completed",
    "output": {
      "document_type": "passport",
      "storage_key": "documents/tenant_123/session_456/document.jpg"
    }
  }
}

Дополнительные поля для step.failed:

ПолеОписание
error_codeКод ошибки
error_messageОписание ошибки
max_attemptsМаксимально допустимое количество попыток (позволяет определить, была ли это последняя попытка)

Дополнительные поля для step.retried:

ПолеОписание
max_attemptsМаксимально допустимое количество попыток
reasonТолько для правок заявителем: "applicant_revision". Заявитель вернулся и изменил пройденный шаг; данные из предыдущего step.completed для этого шага устарели.

События документов (document.*)

document.uploaded:

{
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "step_id": "step_document_capture",
    "storage_key": "documents/tenant_123/session_456/document.jpg"
  }
}

Промежуточные события OCR / liveness / face-match / AML-скрининга / proof-of-address НЕ доставляются как отдельные webhook-события. Их результаты включены в payload события verification.approved / verification.declined / verification.needs_review (массив verifications). Для proof-of-address отправляется только редактированный summary; raw GPS-координаты и storage keys не отправляются. См. примеры выше.

События AML-скрининга (aml.*)

aml.screening_completed:

{
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "match_found": false,
    "risk_score": 15.2,
    "decision": "approve",
    "match_severity": ""
  }
}

aml.hit_found:

{
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "risk_score": 85.5,
    "decision": "review",
    "match_severity": "high"
  }
}

aml.cleared:

{
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "risk_score": 5.0
  }
}

Поле aml.enrichment (Kontur.Focus)

Статус: Доступно с 2026-04-24. Включается на уровне шага AML-скрининга через флаг enable_focus_enrichment в конфигурации flow (по умолчанию true).

После основного AML-скрининга по спискам Kontur.Compliance (санкции, террористы, PEP, банкротство и др.) включается дополнительный этап enrichment через Kontur.Focus API. Этот этап обогащает решение по аппликанту данными из 13 эндпоинтов Focus и формирует объединённое (merged) решение с пометкой источника.

Результаты enrichment включаются в payload aml.screening_completed в виде объекта enrichment:

{
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "match_found": true,
    "risk_score": 78.4,
    "decision": "review",
    "match_severity": "high",
    "enrichment": {
      "inn": "771234567890",
      "inn_source": "ocr",
      "inn_confidence": 0.92,
      "compliance_decision": "review",
      "compliance_match_count": 2,
      "merged_decision": "review",
      "merged_matches_count": 4,
      "merged_risk_score": 78.4,
      "hard_block_reason": null,
      "enrichment_status": "completed",
      "enrichment_completed_at": "2026-04-24T10:15:30Z",
      "call_statuses": {
        "passport_check": "completed",
        "sanctioned_persons": "completed",
        "pep_search": "completed",
        "person_bankruptcy": "completed",
        "person_affiliates": "completed",
        "smz": "skipped",
        "fssp": "skipped_no_inn",
        "general_court_cases": "completed"
      },
      "focus_passport": { "...": "см. ниже" },
      "focus_sanctions": { "...": "см. ниже" },
      "focus_pep": { "...": "см. ниже" },
      "focus_person_bankruptcy": { "...": "см. ниже" },
      "focus_affiliates": { "...": "см. ниже" },
      "focus_smz_status": { "...": "см. ниже" },
      "focus_fssp_summary": { "...": "см. ниже" },
      "focus_court_summary": { "...": "см. ниже" }
    }
  }
}

Поля enrichment:

ПолеТипОписание
innstringИНН аппликанта, обнаруженный пайплайном (источник в inn_source)
inn_sourcestringИсточник ИНН: ocr / compliance / focus_search / focus_affiliates / manual
inn_confidencenumberУверенность в найденном ИНН (0.00–1.00)
compliance_decisionstringРешение Kontur.Compliance до объединения с Focus: approve / decline / review
compliance_match_countintКоличество совпадений по спискам Compliance
merged_decisionstringИтоговое объединённое решение
merged_matches_countintОбщее количество совпадений (Compliance + Focus)
merged_risk_scorenumberИтоговый риск-скор (0.000–1.000 в БД, отнормирован к 0–100 в payload)
hard_block_reasonstring | nullПричина жёсткой блокировки (см. ниже)
enrichment_statusstringpending / running / completed / failed
enrichment_completed_atstring (RFC3339)Когда завершился enrichment
call_statusesobjectСтатус каждого Focus-вызова: completed / error / skipped / skipped_no_inn

Поле focus_passport — Focus-сторонняя проверка паспорта:

{
  "checked_at": "2026-04-24T10:15:00Z",
  "valid": true,
  "source": "focus.checkPassport"
}

Источник: эндпоинт checkPassport Focus API. Используется как второй источник проверки паспорта в дополнение к встроенному passport_check шага верификации.

Поле focus_sanctions — обнаружения по санкционным спискам Focus:

{
  "items": [
    {
      "list_name": "EU consolidated list",
      "matched_name": "IVANOV IVAN",
      "match_type": "exact",
      "confidence": 0.95
    }
  ],
  "checked_at": "2026-04-24T10:15:00Z",
  "source": "focus.sanctionedPersons"
}

Поле focus_pep — попадание в реестр PEP:

{
  "items": [
    {
      "full_name": "ИВАНОВ ИВАН ИВАНОВИЧ",
      "category": "national_pep",
      "position": "Депутат Государственной Думы",
      "checked_at": "2026-04-24T10:15:00Z"
    }
  ],
  "source": "focus.pepSearch"
}

Поле focus_person_bankruptcy — банкротство физических лиц:

{
  "is_bankrupt": false,
  "items": [],
  "checked_at": "2026-04-24T10:15:00Z",
  "source": "focus.personBankruptcy"
}

При обнаружении банкротства поля item включают case_number, arbitr_court, stage (наблюдение / финансовое оздоровление / внешнее управление / конкурсное производство / реализация имущества), start_date, manager_full_name и др.

Поле focus_affiliates — связанные компании (где аппликант является директором / учредителем / бенефициаром):

{
  "items": [
    {
      "ogrn": "1027700123456",
      "inn": "7701234567",
      "company_name": "ООО Пример",
      "role": "director",
      "share_percent": 50.0,
      "is_sanctioned": false,
      "is_bankrupt": false
    }
  ],
  "checked_at": "2026-04-24T10:15:00Z",
  "source": "focus.personAffiliates"
}

Состав полей affiliates можно ограничить через настройку affiliate_fields_visibility в конфигурации шага AML — например, скрыть долю участия от не-администраторов.

Поле focus_smz_status — статус самозанятости (НПД):

{
  "status": "registered",
  "registered_at": "2023-01-15",
  "checked_at": "2026-04-24T10:15:00Z",
  "source": "focus.smzGetStatus"
}

Возможные значения status: registered (является самозанятым), not_registered, unknown (не удалось проверить — например, нет ИНН или отказ ФНС).

Поле focus_fssp_summary — записи в Федеральной службе судебных приставов (только для индивидуальных предпринимателей):

{
  "total_debt_rub": 1250000,
  "open_cases_count": 3,
  "items": [
    {
      "case_number": "12345/24/77001-ИП",
      "debt_amount_rub": 500000,
      "subject": "Алименты",
      "status": "open"
    }
  ],
  "checked_at": "2026-04-24T10:15:00Z",
  "source": "focus.fssp"
}

Поле focus_court_summary — судебные дела (только для ИП):

{
  "total_cases": 5,
  "as_plaintiff": 2,
  "as_defendant": 3,
  "items": [
    {
      "case_number": "А40-12345/2024",
      "court_name": "Арбитражный суд г. Москвы",
      "role": "defendant",
      "status": "in_progress",
      "registration_date": "2024-03-15"
    }
  ],
  "checked_at": "2026-04-24T10:15:00Z",
  "source": "focus.generalCourtCases"
}

Hard-block правила

Поле hard_block_reason непустое означает, что Focus-данные привели к автоматическому отклонению независимо от итогового risk_score. Возможные значения:

ЗначениеОписание
passport_invalidFocus сообщил, что паспорт недействителен
bankruptcy_asset_liquidationАппликант находится на стадии «реализация имущества» в банкротстве
focus_sanctions_hitНайдено совпадение в санкционных списках Focus
affiliate_sanctions_hitСвязанная с аппликантом компания находится под санкциями
fssp_debt_exceeds_thresholdДолг по ФССП превышает настроенный порог hard_block_min_fssp_debt_rub

Каждое из этих правил можно отключить отдельно через конфигурацию шага AML (hard_block_passport_invalid, hard_block_bankruptcy_asset_sale, hard_block_focus_sanctions_hit, hard_block_affiliate_sanctions_hit, hard_block_min_fssp_debt_rub со значением 0).

Per-endpoint флаги

Выполнение каждого Focus-вызова можно включать / выключать индивидуально в конфигурации шага AML (раздел Флоу → Шаг AML-скрининга):

ФлагЭндпоинт FocusОписание
enable_focus_enrichment—Глобальный switch для всего этапа enrichment
enable_sanctioned_persons_checksanctionedPersonsПоиск по санкционным спискам Focus
enable_pep_search_checkpepSearchПоиск в реестре PEP
enable_person_bankruptcy_checkpersonBankruptcyПроверка банкротства физлиц
enable_passport_invalid_checkcheckPassportПроверка паспорта вторым источником
enable_person_affiliatespersonAffiliatesПоиск связанных компаний
enable_smz_checksmzSend + smzGetStatusПроверка статуса самозанятости
enable_fssp_checkfsspПроверка ФССП (только ИП)
enable_court_cases_checkgeneralCourtCasesПроверка судебных дел (только ИП)

Если глобальный enable_focus_enrichment = false, поле enrichment в payload отсутствует, и решение строится только на данных Kontur.Compliance.

Жизненный цикл enrichment и покрытие

Терминальные KYC-события содержат data.enrichment, если для аппликанта существует запись enrichment. Её scope равен applicant: это последний агрегат по аппликанту, а не подтверждение того, что enrichment выполнялся именно для этой сессии. В summary входят status, время наблюдения и завершения, call_statuses по эндпоинтам, объединённое решение, риск-скор и число совпадений (если известны), а также необязательные результаты проверки паспорта и банкротства.

{
  "enrichment": {
    "scope": "applicant",
    "summary": {
      "status": "completed",
      "call_statuses": {"passport_check": "completed", "fssp": "skipped_no_inn"},
      "merged_decision": "approve",
      "merged_risk_score": 0,
      "match_count": 0,
      "passport_invalid": false
    }
  }
}

Отсутствующие результаты означают «неизвестно». Пропущенный или завершившийся ошибкой эндпоинт не является отрицательной проверкой. Известные нулевые оценки и счётчики, а также известные отрицательные находки передаются явно. Исходные ответы поставщиков и идентификаторы (ИНН, СНИЛС, номер паспорта) в эту сводку не входят.

Enrichment может завершиться уже после терминального события. Подпишитесь на verification.enrichment.updated, чтобы получать последующие обновления (завершён или ошибка); они коррелируются по session_id, applicant_id и номеру попытки и используют ту же сводку уровня аппликанта. Если терминальный снимок недоступен, это отражается в enrichment_errors; такой результат нельзя считать чистым.

Итоговая идентичность и данные

Автоматические и проверенные вручную терминальные KYC-события и события решений используют одну и ту же проекцию данных. applicant.full_name остаётся прежним отображаемым значением. full_name_latin передаётся только для наблюдаемого имени в латинице. full_name_native и full_name_original сохраняют исходное написание в документе, если оно доступно; транслитерация не выдумывается. name_source указывает результат документа, шаг и попытку; name_evidence сохраняет альтернативы сверки. localized сохраняет подписи полей провайдера и добавляет канонические псевдонимы. Отсутствующие имена остаются отсутствующими для flow без данных о личности.

Терминальные снимки также включают доступные verifications, decision_reasons, custom_steps, document_collections, media_files, безопасные risk_signals и сводки по устройствам. Личность аппликанта определяется по текущим попыткам. Записи верификации сохраняют is_current, чтобы потребители могли отличать устаревшие данные. Сбои необязательного чтения отображаются стабильными кодами в enrichment_errors, а не молчаливо подразумевают пустую успешную проверку.

Все события ссылок содержат полную информацию о ссылке верификации:

{
  "type": "link.consumed",
  "data": {
    "link": {
      "id": "550e8400-e29b-41d4-a716-446655440003",
      "tenant_id": "550e8400-e29b-41d4-a716-446655440000",
      "flow_id": "550e8400-e29b-41d4-a716-446655440002",
      "external_user_id": "user_12345",
      "external_reference": "ref_abc123",
      "status": "consumed",
      "session_id": "550e8400-e29b-41d4-a716-446655440001",
      "created_at": "2026-02-11T10:00:00Z",
      "activated_at": "2026-02-11T10:05:00Z",
      "consumed_at": "2026-02-11T12:00:00Z",
      "expires_at": "2026-02-14T10:00:00Z",
      "locale": "en"
    }
  }
}

Полная схема полей ссылки доступна в Справочнике API (Swagger / OpenAPI).

События OCR (verification.ocr.*)

verification.ocr.corrected — оператор отредактировал результаты OCR через панель администратора:

{
  "type": "verification.ocr.corrected",
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "applicant_id": "550e8400-e29b-41d4-a716-446655440005",
    "verification_id": "550e8400-e29b-41d4-a716-446655440099",
    "fields": {
      "first_name": "JANE",
      "last_name": "DOE",
      "date_of_birth": "1990-05-15"
    }
  }
}

События Manual Review Resubmit (verification.resubmit_*)

События запроса повторной загрузки шага оператором ручного ревью.

request_id идентифицирует отдельную отправку и связывает её с завершением пересдачи. scope равен step (также значение по умолчанию для старых событий) или flow для полного KYB. Для flow поле step_id пустое. Непустой собственный client_message позволяет не выбирать reason_code; если указаны оба значения, передаются оба. Предыдущие отправки сохраняются в истории. Внутренние заметки не передаются.

События ручного отклонения (verification.declined, session.completed и события решения KYB) содержат текущий decision_id, необязательные decision_reason_codes и собственный публичный client_message из того же сохранённого решения. Для отклонения только с комментарием причина не выдумывается. Старый комментарий не подставляется вместо текущего решения; internal_notes остаются внутренними.

verification.resubmit_requested — оператор запросил у заявителя повторную загрузку шага:

{
  "type": "verification.resubmit_requested",
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "flow_id": "550e8400-e29b-41d4-a716-446655440003",
    "external_id": "user_12345",
    "applicant_id": "550e8400-e29b-41d4-a716-446655440005",
    "step_id": "550e8400-e29b-41d4-a716-446655440010",
    "step_type": "document_capture",
    "reason_code": "blurry",
    "client_message": "Фотография нечёткая, перезагрузите, пожалуйста",
    "attempt_number": 1,
    "requested_by_id": "550e8400-e29b-41d4-a716-446655440100",
    "requested_by_name": "Reviewer Name",
    "expires_at": "2026-05-04T12:00:00Z"
  }
}

Допустимые значения reason_code: illegible, blurry, cropped, glare, wrong_document, expired_document, partial.

verification.resubmit_fulfilled — заявитель повторно загрузил запрошенный шаг:

Событие означает, что для запрошенного шага записана новая терминальная попытка; оно не означает успешное прохождение шага или одобрение всей проверки. Payload содержит тот же external_id, который был отправлен в событии запроса.

{
  "type": "verification.resubmit_fulfilled",
  "data": {
    "session_id": "550e8400-e29b-41d4-a716-446655440002",
    "flow_id": "550e8400-e29b-41d4-a716-446655440003",
    "external_id": "user_12345",
    "applicant_id": "550e8400-e29b-41d4-a716-446655440005",
    "step_id": "550e8400-e29b-41d4-a716-446655440010",
    "reason_code": "blurry",
    "attempt_number": 1,
    "fulfilled_at": "2026-04-27T18:30:00Z"
  }
}

Лимиты Manual Review Resubmit: до 3 запросов на сессию, TTL запроса 7 дней. Если заявитель не выполнит повторную загрузку до истечения TTL, запрос автоматически отменяется.

Часто задаваемые вопросы

Утрата секретного ключа вебхука

Секретный ключ вебхука (с префиксом whsec_) невозможно восстановить — он отображается только один раз при создании endpoint.

Порядок действий:

  1. Откройте панель администратора OneKYC.
  2. Перейдите в раздел Вебхуки.
  3. Нажмите кнопку «Ротация секрета» рядом с соответствующим endpoint.
  4. Скопируйте новый секретный ключ — он отображается однократно.
  5. Обновите секретный ключ в конфигурации вашего приложения.

Обратите внимание: в ходе ротации оба секретных ключа (прежний и новый) действуют одновременно в течение периода грации продолжительностью 24 часа. Это обеспечивает непрерывность доставки вебхуков на время обновления. В заголовке X-Onekyc-Signature будут присутствовать как v1=<прежняя_подпись>, так и v2=<новая_подпись>.

По истечении периода грации новый ключ становится основным: заголовок содержит только v1=<новая_подпись>. Старый ключ больше не подписывает доставки, даже если фоновая очистка в базе данных ещё не выполнена. Получатель должен принимать новую подпись в v2 в течение периода грации и в v1 после его завершения; используйте сравнение HMAC за постоянное время и сохраняйте проверки временной метки и защиты от повторов.

Чтобы завершить ротацию досрочно, убедитесь, что получатель использует новый ключ, затем выберите Завершить ротацию в консоли. API-клиенты могут вызвать POST /admin/v1/webhooks/endpoints/{id}/rotate/complete с телом {"rotation_expires_at":"<срок, возвращённый API endpoint>"}. Устаревший срок возвращает 409; обновите данные endpoint, а не повторяйте изменение секрета автоматически. Завершение немедленно выводит старый ключ из использования.

Отсутствие входящих вебхуков

В случае прекращения поступления вебхуков выполните следующие проверки:

1. Статус endpoint в панели администратора

  • Откройте раздел Вебхуки в панели администратора.
  • Проверьте статус endpoint (активен / отключён / ошибка).
  • Просмотрите журнал доставки — в нём указаны коды ответов и описания ошибок.

2. Доступность вашего сервера

  • URL-адрес должен быть публично доступен по протоколу HTTPS (HTTP не поддерживается).
  • Убедитесь, что межсетевой экран не блокирует входящие запросы от OneKYC.
  • Проверьте валидность SSL-сертификата (самоподписанные сертификаты не поддерживаются).

3. Код обработки вебхука

  • Ваш endpoint должен возвращать ответ с HTTP-кодом 2xx (200, 201, 204) в течение 30 секунд.
  • Если обработка требует больше времени, рекомендуется немедленно возвращать ответ 200 OK и выполнять обработку асинхронно.
  • Проверьте журнал вашего приложения на наличие ошибок валидации подписи.

4. Типы событий

  • Убедитесь, что в настройках endpoint включены необходимые типы событий.
  • При выборе варианта «Все события» проверьте, что события действительно генерируются (создаются сессии, выполняются верификации и т. д.).

5. Очередь недоставленных сообщений (Dead Letter Queue)

Если система не смогла доставить вебхук после 5 попыток (с экспоненциальным увеличением интервала до 24 часов), событие помещается в Dead Letter Queue. Недоставленные события доступны для просмотра в панели администратора, откуда можно инициировать повторную доставку вручную.

Тестирование endpoint:

Для проверки работоспособности endpoint нажмите кнопку «Отправить тестовый вебхук» в панели администратора. В открывшемся диалоговом окне выберите тип события (например, verification.approved) — система направит тестовый payload данного события с корректной подписью HMAC-SHA256. Идентификатор тестового события имеет префикс evt_test_.

Обращение в службу поддержки

При возникновении затруднений в работе с вебхуками воспользуйтесь следующими каналами:

1. Документация и примеры

  • Ознакомьтесь с материалами данного раздела документации и приведёнными примерами кода.
  • Обратитесь к Справочнику API (Swagger / OpenAPI) для получения детальной спецификации.

2. Техническая поддержка

  • Email: info@onekyc.io
  • Telegram: [@OneKYC_Support](https://t.me/OneKYC_Support)
  • Часы работы: Пн–Пт, 10:00–19:00 (GMT+6, Алматы)

3. Критические проблемы

Для решения критических проблем, блокирующих работу в производственной среде:

  • Направьте обращение в службу поддержки с пометкой «URGENT» в теме письма.
  • Укажите tenant_id, URL-адрес endpoint и примеры записей из журнала ошибок.
  • Ответ будет предоставлен в течение 2 часов в рабочее время.

4. Сообщения об ошибках и предложения по улучшению

В случае обнаружения ошибки в системе вебхуков или при наличии предложений по улучшению:

  • Создайте обращение через личный кабинет в панели администратора.
  • Или направьте подробное описание на адрес info@onekyc.io.

Подробная информация о формате payload, заголовках и политике повторных попыток доступна в Справочнике API (Swagger / OpenAPI).

Вебхуки комментариев ревью

Новый комментарий к задаче KYC создаёт событие verification.comment.created, к задаче KYB — kyb.review.comment.created. Одинаковый контракт действует для админки клиента, платформенной консоли ревью и клиентского API. Комментарий не меняет решение проверки и не завершает сессию.

Подпишитесь на нужный тип события. Существующие подписки * также получают новые события. Используются обычные подпись вебхука, фильтрация sandbox/production, повторные попытки и журнал доставок. Возможны повторные доставки: устраняйте дубли по id события или idempotency_key. Порядок получения не гарантирован.

Поле data содержит scope, task_id, session_id, flow_id, external_id и объект comment с полями id, content, created_at, author_type. Для KYC передаётся applicant_id, для KYB — business_id, если он задан. Типы авторов: admin_user, superadmin_user, api_key, system. Идентификаторы и имена сотрудников в вебхук не включаются.

Пример данных события для KYC:

{
  "scope": "kyc",
  "task_id": "550e8400-e29b-41d4-a716-446655440010",
  "session_id": "550e8400-e29b-41d4-a716-446655440001",
  "flow_id": "550e8400-e29b-41d4-a716-446655440002",
  "external_id": "customer_123",
  "applicant_id": "550e8400-e29b-41d4-a716-446655440099",
  "comment": {
    "id": "550e8400-e29b-41d4-a716-446655440011",
    "content": "Please provide a clearer document photo.",
    "created_at": "2026-10-16T08:00:00.123456Z",
    "author_type": "admin_user"
  }
}

Все комментарии задачи доступны клиенту-интегратору. Внутренние заметки решения internal_notes и сообщение повторной загрузки client_message — отдельные поля; они не создают события комментариев. События создаются только для новых комментариев; исторические комментарии не рассылаются.

Получение комментариев: GET /tenant/v1/kyc/review-tasks/{task_id}/comments с правом reviews:read или GET /tenant/v1/kyb/review-tasks/{task_id}/comments с правом kyb.reviews:read. Создание: POST по тому же пути с соответствующим правом записи. Успешный ответ создания дополнен comment_id и created_at, совпадающими с сохранённым комментарием и снимком в вебхуке. Каждый успешный POST создаёт новый комментарий; дедупликация вебхука не делает повторные запросы создания идемпотентными. При неопределённом результате запроса сверяйтесь с API комментариев до повторной отправки.

Комментарий, событие и запись outbox сохраняются одной транзакцией. Ошибка сохранения события откатывает создание комментария. HTTP-доставка выполняется асинхронно после фиксации транзакции: успешный ответ создания не подтверждает доставку вебхука клиенту; проверяйте журнал доставок.

На этой странице