POST на
callback_url, настроенный для вашего сайта. Это основной и самый надёжный способ
узнавать об оплате — webhook приходит сразу после изменения, без задержек polling’а.
Webhook’и идут в одну сторону: от платформы к вашему серверу. Запросы к Public API
(создание депозитов, выплат и т. д.) подписываются по-другому — см.
Аутентификация. Здесь описана проверка входящих к вам webhook’ов,
которые платформа подписывает ключом
callback_secret вашего сайта.Как это работает
Событие происходит
Депозит финализируется, выплата уходит в сеть и т. п. — статус меняется на стороне платформы.
Платформа формирует и подписывает webhook
Тело сериализуется в JSON, подписывается
HMAC-SHA256 по callback_secret сайта,
добавляются заголовки X-Signature / X-Timestamp / X-Event-Type / X-Event-Id.Заголовки доставки
Каждый webhook приходит со следующими заголовками (имена регистронезависимы):| Заголовок | Значение |
|---|---|
X-Signature | HMAC-SHA256 в нижнем регистре hex от сырого тела запроса, ключ — callback_secret сайта |
X-Timestamp | Unix-время отправки в секундах (момент попытки доставки) |
X-Event-Type | тип события — deposit.* либо payout.* (см. События) |
X-Event-Id | UUID логического события — для идемпотентности (один и тот же id на все повторы) |
Content-Type | application/json |
Об X-Event-Id и идемпотентности
X-Event-Id детерминирован: один логический event (тот же ресурс + тот же тип события)
всегда получает один и тот же id — стабильный между повторными попытками и даже между
перезапусками платформы. Значение в заголовке X-Event-Id совпадает с полем eventId в теле.
Используйте его как ключ дедупликации: если событие с этим id уже обработано — ответьте
200 и не выполняйте побочных эффектов повторно.
События
X-Event-Type (и поле eventType в теле) принимает значения:
X-Event-Type | Когда отправляется |
|---|---|
deposit.tx_detected | замечена первая входящая транзакция на адрес (ещё не подтверждена) |
deposit.finalized | депозит финализирован — статус paid, paid_over или wrong_amount |
deposit.failed | депозит не удался — fail / system_fail / истёк срок |
deposit.refunded | отправлен возврат (refund_paid) |
payout.broadcasted | выплата отправлена в сеть |
payout.confirmed | выплата подтверждена в сети |
payout.failed | выплата не удалась |
Финальный статус депозита смотрите в поле
deposit.status тела события (например,
deposit.finalized может нести status: "paid", "paid_over" или "wrong_amount").
Подробнее о статусах — на странице Депозиты и Выплаты.
Тестовый webhook из эндпоинта /test приходит с особым X-Event-Type: webhook.test —
это не боевое событие.Тело webhook’а
Тело — JSON c полямиeventType, eventId и вложенным объектом deposit либо payout
(в зависимости от ресурса). Поле eventId дублирует заголовок X-Event-Id.
Поля объекта deposit
UUID депозитной транзакции на платформе.
Ваш
order_id, переданный при создании депозита (ключ идемпотентности на стороне сайта).Статус депозита:
paid, paid_over, wrong_amount, fail, system_fail, refund_paid и др.Код актива, например
USDT_TRC20, USDT_TON, TRX, TON.Ожидаемая сумма (строка — без потери точности).
Фактически полученная сумма (строка).
null, если приход ещё не зафиксирован.Адрес назначения депозита.
Memo/comment для memo-based сетей (TON).
null для account-based сетей (TRON).Хеш входящей транзакции.
null, если транзакция ещё не замечена.Текущее число подтверждений.
Сколько подтверждений требуется для финализации.
ISO-8601 момент изменения статуса.
Поля объекта payout
UUID выплаты на платформе.
Ваш
order_id, переданный при создании выплаты.Статус выплаты:
broadcasted, confirmed, failed и др.Код актива выплаты.
Сумма выплаты (строка, отформатирована до
decimals актива).Адрес получателя.
Memo/comment получателя для memo-based сетей.
null для account-based.Реальный on-chain хеш транзакции (для TON резолвится мониторингом с задержкой).
null, пока не отправлено.Готовая ссылка на транзакцию в обозревателе сети.
null, если ещё недоступна.Текущее число подтверждений.
null, пока нет on-chain транзакции.Требуемое число подтверждений.
null, пока нет on-chain транзакции.Причина ошибки для
payout.failed. null для успешных событий.ISO-8601 момент изменения статуса.
Проверка подписи
СверяйтеX-Signature с HMAC-SHA256 от сырых байтов запроса. Прочитайте тело
как буфер до парсинга JSON — иначе повторная сериализация изменит подпись.
Политика повторных попыток
Доставка считается успешной, если ваш endpoint вернулHTTP 2xx в течение 10 секунд
(тайм-аут настраивается оператором в диапазоне 1–60 секунд). Любой другой исход — не-2xx
ответ, тайм-аут или сетевая ошибка — считается неудачей, и платформа повторяет доставку
по фиксированному расписанию с возрастающими интервалами:
Максимум 8 попыток по умолчанию (оператор может настроить от 1 до 20). После исчерпания
попыток доставка помечается как
fail; такой webhook можно вручную переотправить через
эндпоинт resend.
Если для сайта включён режим
allow_private_hosts, callback_url может указывать на
адрес во внутренней сети (например, CMS обменника на том же сервере). По умолчанию
внутренние/зарезервированные адреса отклоняются SSRF-защитой, и такие попытки логируются
как неудачные.Тест и переотправка
Два служебных эндпоинта Public API помогают отладить интеграцию. Оба требуют стандартной HMAC-аутентификации Public API (заголовкиX-Api-Id / X-Timestamp /
X-Signature) и возвращают результат в общем конверте ответа { "ok": true, "data": ... }.
| Эндпоинт | Действие |
|---|---|
POST /v1/public/webhooks/test | Поставить в очередь один тестовый подписанный webhook на ваш callback_url (без повторов). |
POST /v1/public/webhooks/resend/{eventId} | Переотправить ранее сгенерированный webhook по его X-Event-Id (с обычным расписанием повторов). |
POST /v1/public/webhooks/test
Ставит в очередь одну попытку доставки тестового webhook’а с теми же заголовками и подписью,
что и у боевых событий, но с особым типом X-Event-Type: webhook.test. Удобно проверить, что
ваш endpoint доступен, принимает POST и корректно валидирует X-Signature. Повторов нет —
ровно одна попытка.
Тело тестового webhook’а, которое получит ваш callback_url:
Тело тестового webhook
data:
| Поле | Тип | Описание |
|---|---|---|
queued | boolean | true — тестовый webhook поставлен в очередь доставки. |
url | string | callback_url, на который будет доставлен тест. |
eventId | string | UUID этого тестового события (придёт в X-Event-Id). |
POST /v1/public/webhooks/resend/{eventId}
Переотправляет ранее сгенерированный webhook по его X-Event-Id. Тело и URL берутся из лога
доставки, подпись пересчитывается под текущий callback_secret. Повтор использует обычное
расписание повторных попыток. Переотправлять можно только события своего сайта —
доступ к чужим eventId невозможен (вернётся 404).
data:
| Поле | Тип | Описание |
|---|---|---|
enqueued | boolean | true — переотправка поставлена в очередь. |
eventId | string | X-Event-Id переотправляемого события. |
Частые вопросы
Подпись не сходится, хотя секрет верный
Подпись не сходится, хотя секрет верный
Почти всегда причина в том, что подпись считается не от сырого тела. Прочитайте тело как
буфер/строку до парсинга JSON и считайте
HMAC-SHA256 именно от этих байтов.
Повторная сериализация распарсенного объекта меняет порядок ключей и пробелы — подпись
не совпадёт. В Express используйте express.raw(), во Flask — request.get_data().Webhook не приходит совсем
Webhook не приходит совсем
Проверьте по порядку: настроен ли
callback_url для сайта; доступен ли ваш endpoint
извне (или включён ли allow_private_hosts, если он во внутренней сети); не режут ли
запрос ваш firewall/WAF. Затем вызовите POST /v1/public/webhooks/test — он поставит
тестовую доставку и вернёт url и eventId, по которым видно, куда платформа пыталась
доставить.Получаю один и тот же webhook несколько раз
Получаю один и тот же webhook несколько раз
Это ожидаемо: при не-2xx ответе или тайм-ауте платформа повторяет доставку (до 8 раз по
умолчанию), а одно событие может также прийти повторно после ручного resend. На все
повторы один и тот же
X-Event-Id. Дедуплицируйте по нему: при уже обработанном id
отвечайте 200 без побочных эффектов.Сколько у меня времени на ответ?
Сколько у меня времени на ответ?
10 секунд по умолчанию (оператор может задать 1–60 секунд). Если обработка дольше —
примите webhook, поставьте задачу в свою очередь и сразу верните
200, а тяжёлую логику
выполняйте асинхронно. Долгий ответ платформа считает неудачей и повторит доставку.Чем отличаются deposit.tx_detected и deposit.finalized?
Чем отличаются deposit.tx_detected и deposit.finalized?
deposit.tx_detected — первая входящая транзакция замечена, но ещё не набрала нужных
подтверждений (receivedAmount/txHash могут быть уже заполнены, но статус не финальный).
deposit.finalized — депозит достиг requiredConfirmations; смотрите deposit.status
(paid / paid_over / wrong_amount), чтобы понять исход. Зачислять средства клиенту
стоит на deposit.finalized со статусом paid/paid_over, не на tx_detected.resend вернул 404 — почему?
resend вернул 404 — почему?
Указанный
eventId не найден среди событий вашего сайта. Переотправлять можно только
свои события. Проверьте, что eventId взят из реально доставлявшегося ранее webhook’а
(заголовок X-Event-Id или поле eventId тела) и принадлежит тому же сайту, чьим
ключом подписан запрос.Смежные страницы
- Аутентификация — как подписывать запросы к Public API (test/resend).
- Депозиты — статусы депозитов и поля ответа.
- Выплаты — статусы выплат и поля ответа.
- Ошибки и конверт ответа — формат
{ ok, data }/{ ok, error }и коды ошибок.