Вебхуки
Система вебхуков 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: покрытие и решение |
Порядок событий для сессий с ручным ревью
Если сессия требует ручной проверки оператором, порядок отправки событий следующий:
verification.needs_review— сразу после того, как автоматическая проверка завершилась с решением "нужна ручная проверка", или когда оператор вручную возвращает терминальную сессию на ревью.session.completedне отправляется на этом этапе — сессия ещё не финализирована.verification.approvedилиverification.declined— после финального решения оператора.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_completed | AML-проверка завершена |
aml.hit_found | Обнаружено совпадение в базах AML |
aml.cleared | AML-проверка завершена без совпадений |
Ссылки верификации (4 события)
| Событие | Описание |
|---|---|
link.created | Ссылка верификации создана |
link.consumed | Ссылка использована |
link.expired | Срок действия ссылки истёк |
link.revoked | Ссылка отозвана |
Формат запроса и подпись
OneKYC направляет на указанный URL-адрес POST-запрос с телом в формате JSON. Каждый запрос содержит следующие заголовки:
| Заголовок | Описание |
|---|---|
Content-Type | application/json |
X-Onekyc-Signature | Подпись в формате t=<timestamp>,v1=<signature> |
X-Onekyc-Event-Type | Тип события (например, session.created) |
X-Onekyc-Event-Id | UUID события |
X-Onekyc-Timestamp | Unix-время отправки |
User-Agent | OneKYC-Webhook/1.0 |
Имена HTTP-заголовков нечувствительны к регистру (RFC 7230 §3.2). На стороне приёмника используйте сравнение без учёта регистра (большинство HTTP-фреймворков делает это автоматически).
Проверка подписи
Для верификации подлинности вебхука выполните следующие шаги:
- Извлеките значения
t(timestamp) иv1(signature) из заголовкаX-Onekyc-Signature. - Сформируйте строку для подписи:
{timestamp}.{raw_body}(числовое значение изt, символ точки, затем тело запроса в исходном виде). - Вычислите HMAC-SHA256 с использованием вашего секретного ключа вебхука (
whsec_...). - Сравните полученное значение с
v1из заголовка (используйте сравнение с постоянным временем выполнения — constant-time comparison). - Убедитесь, что значение 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:
| Поле | Тип | Описание |
|---|---|---|
inn | string | ИНН аппликанта, обнаруженный пайплайном (источник в inn_source) |
inn_source | string | Источник ИНН: ocr / compliance / focus_search / focus_affiliates / manual |
inn_confidence | number | Уверенность в найденном ИНН (0.00–1.00) |
compliance_decision | string | Решение Kontur.Compliance до объединения с Focus: approve / decline / review |
compliance_match_count | int | Количество совпадений по спискам Compliance |
merged_decision | string | Итоговое объединённое решение |
merged_matches_count | int | Общее количество совпадений (Compliance + Focus) |
merged_risk_score | number | Итоговый риск-скор (0.000–1.000 в БД, отнормирован к 0–100 в payload) |
hard_block_reason | string | null | Причина жёсткой блокировки (см. ниже) |
enrichment_status | string | pending / running / completed / failed |
enrichment_completed_at | string (RFC3339) | Когда завершился enrichment |
call_statuses | object | Статус каждого 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_invalid | Focus сообщил, что паспорт недействителен |
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_check | sanctionedPersons | Поиск по санкционным спискам Focus |
enable_pep_search_check | pepSearch | Поиск в реестре PEP |
enable_person_bankruptcy_check | personBankruptcy | Проверка банкротства физлиц |
enable_passport_invalid_check | checkPassport | Проверка паспорта вторым источником |
enable_person_affiliates | personAffiliates | Поиск связанных компаний |
enable_smz_check | smzSend + smzGetStatus | Проверка статуса самозанятости |
enable_fssp_check | fssp | Проверка ФССП (только ИП) |
enable_court_cases_check | generalCourtCases | Проверка судебных дел (только ИП) |
Если глобальный 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, а не молчаливо подразумевают пустую успешную проверку.
События ссылок верификации (link.*)
Все события ссылок содержат полную информацию о ссылке верификации:
{
"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.
Порядок действий:
- Откройте панель администратора OneKYC.
- Перейдите в раздел Вебхуки.
- Нажмите кнопку «Ротация секрета» рядом с соответствующим endpoint.
- Скопируйте новый секретный ключ — он отображается однократно.
- Обновите секретный ключ в конфигурации вашего приложения.
Обратите внимание: в ходе ротации оба секретных ключа (прежний и новый) действуют одновременно в течение периода грации продолжительностью 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-доставка выполняется асинхронно после фиксации транзакции: успешный ответ создания не подтверждает доставку вебхука клиенту; проверяйте журнал доставок.