Справочник вебхуков
Полный каталог событий с точными payload, заголовки и семантика доставки, лесенка ретраев 1м→12ч и правила идемпотентности.
Всё о получении событий ZeroKYC Pay: каталог событий, точная форма payload, заголовки доставки, лесенка ретраев и правила идемпотентности.
Webhook-эндпоинты настраиваются в кабинете в разделе Вебхуки: URL плюс секрет эндпоинта (whsec_…). Каждый активный эндпоинт получает все события; конкретный инвойс может переопределить URL полем webhook_url при создании.
События
| Событие | Когда срабатывает |
|---|---|
payment.detected | На цепи найдена транзакция, совпадающая с одним из вариантов инвойса (мемпул или ранние подтверждения) |
payment.confirmed | Достигнуто нужное число подтверждений — сигнал «отгружать товар» |
payment.underpaid | Пришёл платёж ниже приемлемого диапазона; инвойс становится underpaid и требует вашего решения |
invoice.expired | TTL истёк без валидного платежа |
invoice.canceled | Инвойс отменён через POST /v1/invoices/{id}/cancel до оплаты |
webhook.test | Вы нажали Отправить тестовое событие в кабинете — подписанная smoke-проверка обработчика |
invoice.created существует только в журнале событий (GET /v1/events), вебхуком он не доставляется.
Мультивариантные инвойсы (payment_currency=any) подтверждаются по первому валидному платежу; остальные варианты закрываются автоматически.
Payload
Каждая доставка — JSON-конверт: id события, type, created_at и объект data, специфичный для события:
{
"id": "evt_5c1a...",
"type": "payment.confirmed",
"created_at": "2026-09-06T14:02:33Z",
"data": {
"invoice_id": "inv_9f2k...",
"order_id": "order-1042",
"amount": "49.00",
"base_currency": "USD",
"option": {
"asset": "TON",
"network": "ton",
"amount_crypto": "9.81",
"paid_amount": "9.81"
},
"tx": {
"hash": "9d24...",
"confirmations": 1,
"explorer_url": "https://tonviewer.com/transaction/9d24...",
"output_index": 0
},
"metadata": { "customer_id": "cust_8821" }
}
}
У payment.detected, payment.confirmed и payment.underpaid структура идентична. По полям:
idглобально уникален для события — сохраняйте его и дедуплицируйте (доставка at-least-once).order_idиmetadataповторяют то, что вы отправили при создании инвойса;order_id— ваш ключ соединения.amount— сумма инвойса вbase_currency; крипто-цифры живут вoption(amount_crypto= к оплате,paid_amount= фактически наблюдается на цепи).tx.explorer_urlведёт на транзакцию в эксплорере актива;output_indexразличает UTXO/log-выходы.- Переплаты подтверждаются штатно:
option.paid_amount— наблюдаемая правда.
События жизненного цикла компактнее:
{
"id": "evt_7b2c...",
"type": "invoice.expired",
"created_at": "2026-09-06T20:20:11Z",
"data": {
"invoice_id": "inv_9f2k...",
"order_id": "order-1042",
"amount": "49.00",
"base_currency": "USD"
}
}
invoice.canceled несёт только invoice_id и order_id; webhook.test — endpoint_id, url и строку message.
Заголовки доставки
| Заголовок | Значение |
|---|---|
Content-Type | application/json |
User-Agent | ZeroKYC-Pay-Webhooks/1.0 |
X-ZKP-Signature | t=<unix seconds>,v1=<hex hmac-sha256> |
X-ZKP-Event-Id | Тот же id, что в payload — дедупликация без парсинга тела |
X-ZKP-Event-Type | Тот же type — маршрутизация без парсинга тела |
Подпись — HMAC-SHA256 по строке {timestamp}.{raw_body} секретом эндпоинта; окно таймстемпа — ±300 секунд. Проверяйте до доверия — полные примеры Node/PHP/Python в проверке HMAC.
Семантика доставки и ретраи
- Успех = любой
2xxв течение 10 секунд. Всё остальное —4xx,5xx, таймаут, ошибка TLS — планирование ретрая. - Редиректы не преследуются.
3xxсчитается провалом: указывайте конечный URL эндпоинта, а не ссылку-редирект. - Лесенка ретраев:
| Попытка | Задержка |
|---|---|
| 1 | сразу |
| 2 | 1 минута |
| 3 | 5 минут |
| 4 | 30 минут |
| 5 | 2 часа |
| 6 | 12 часов → затем failed |
- Когда лесенка исчерпана, доставка помечается
failed, в кабинет приходит уведомление. Ручной retry (Вебхуки → журнал доставок или по инвойсу) сбрасывает счётчик попыток и срабатывает сразу. - Секрет и URL эндпоинта ротируются в разделе Вебхуки в любой момент; кнопка Отправить тестовое событие доставляет подписанный
webhook.test— проверьте новую конфигурацию end-to-end.
Сверка (reconciliation)
Обработчики должны быть идемпотентны, а API даёт два способа перепроверки:
GET /v1/invoices/{id}всегда возвращает авторитетное состояние инвойса — считайте вебхуки подсказкой, а не истиной в последней инстанции.GET /v1/events?limit=100проигрывает журнал событий (включая типы только-для-журнала вродеinvoice.created), с фильтрамиtypeиcreated_afterи курсорной пагинацией — используйте его для восстановления после простоя.
Rescan-задача перепроверяет все открытые инвойсы каждые 3 минуты, поэтому пропущенные в реальном времени события цепи самолечатся: payment.confirmed может прийти заметно позже самой транзакции.
Чек-лист потребителя
- Проверяйте HMAC из
X-ZKP-Signatureи окно ±300 с, прежде чем трогать payload. - Дедуплицируйте по
event.id(илиX-ZKP-Event-Id) — одно событие может прийти больше раза. - Исполняйте заказ только по
payment.confirmed;payment.detected— для раннего фидбэка покупателю,payment.underpaid— для ручного разбора. - Отвечайте
2xxбыстро; тяжёлую работу выносите в фон.