Skip to main content
При каждом изменении статуса депозита или выплаты платформа отправляет POST на callback_url, настроенный для вашего сайта. Это основной и самый надёжный способ узнавать об оплате — webhook приходит сразу после изменения, без задержек polling’а.
Webhook’и идут в одну сторону: от платформы к вашему серверу. Запросы к Public API (создание депозитов, выплат и т. д.) подписываются по-другому — см. Аутентификация. Здесь описана проверка входящих к вам webhook’ов, которые платформа подписывает ключом callback_secret вашего сайта.

Как это работает

1

Событие происходит

Депозит финализируется, выплата уходит в сеть и т. п. — статус меняется на стороне платформы.
2

Платформа формирует и подписывает webhook

Тело сериализуется в JSON, подписывается HMAC-SHA256 по callback_secret сайта, добавляются заголовки X-Signature / X-Timestamp / X-Event-Type / X-Event-Id.
3

Доставка на ваш callback_url

POST на ваш endpoint. Успех — ответ HTTP 2xx в течение 10 секунд.
4

Вы проверяете подпись и обрабатываете

Сверьте X-Signature constant-time, дедуплицируйте по X-Event-Id, ответьте 200.

Заголовки доставки

Каждый webhook приходит со следующими заголовками (имена регистронезависимы):
Всегда проверяйте X-Signature до обработки тела. Посчитайте HMAC-SHA256 от сырых байтов запроса с вашим callback_secret и сравните constant-time. При несовпадении — отклоняйте запрос (401) и не выполняйте бизнес-логику. Подпись считается именно от сырого тела — не от распарсенного и заново сериализованного JSON (порядок ключей и пробелы изменят результат).

Об X-Event-Id и идемпотентности

X-Event-Id детерминирован: один логический event (тот же ресурс + тот же тип события) всегда получает один и тот же id — стабильный между повторными попытками и даже между перезапусками платформы. Значение в заголовке X-Event-Id совпадает с полем eventId в теле. Используйте его как ключ дедупликации: если событие с этим id уже обработано — ответьте 200 и не выполняйте побочных эффектов повторно.

События

X-Event-Type (и поле eventType в теле) принимает значения:
Финальный статус депозита смотрите в поле 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

string
UUID депозитной транзакции на платформе.
string | null
Ваш order_id, переданный при создании депозита (ключ идемпотентности на стороне сайта).
string
Статус депозита: paid, paid_over, wrong_amount, fail, system_fail, refund_paid и др.
string
Код актива, например USDT_TRC20, USDT_TON, TRX, TON.
string
Ожидаемая сумма (строка — без потери точности).
string | null
Фактически полученная сумма (строка). null, если приход ещё не зафиксирован.
string
Адрес назначения депозита.
string | null
Memo/comment для memo-based сетей (TON). null для account-based сетей (TRON).
string | null
Хеш входящей транзакции. null, если транзакция ещё не замечена.
number
Текущее число подтверждений.
number
Сколько подтверждений требуется для финализации.
string
ISO-8601 момент изменения статуса.

Поля объекта payout

string
UUID выплаты на платформе.
string | null
Ваш order_id, переданный при создании выплаты.
string
Статус выплаты: broadcasted, confirmed, failed и др.
string
Код актива выплаты.
string
Сумма выплаты (строка, отформатирована до decimals актива).
string
Адрес получателя.
string | null
Memo/comment получателя для memo-based сетей. null для account-based.
string | null
Реальный on-chain хеш транзакции (для TON резолвится мониторингом с задержкой). null, пока не отправлено.
string | null
Готовая ссылка на транзакцию в обозревателе сети. null, если ещё недоступна.
number | null
Текущее число подтверждений. null, пока нет on-chain транзакции.
number | null
Требуемое число подтверждений. null, пока нет on-chain транзакции.
string | null
Причина ошибки для payout.failed. null для успешных событий.
string
ISO-8601 момент изменения статуса.

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

Сверяйте X-Signature с HMAC-SHA256 от сырых байтов запроса. Прочитайте тело как буфер до парсинга JSON — иначе повторная сериализация изменит подпись.
callback_secret выдаётся при настройке сайта и доступен только владельцу инстанса. Он никогда не передаётся в открытом виде по сети. Если подозреваете компрометацию — ротируйте секрет в админ-кабинете; после ротации проверяйте подпись новым значением.

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

Доставка считается успешной, если ваш endpoint вернул HTTP 2xx в течение 10 секунд (тайм-аут настраивается оператором в диапазоне 1–60 секунд). Любой другой исход — не-2xx ответ, тайм-аут или сетевая ошибка — считается неудачей, и платформа повторяет доставку по фиксированному расписанию с возрастающими интервалами:
1

Попытка 2 — через 30 секунд

2

Попытка 3 — через 2 минуты

3

Попытка 4 — через 10 минут

4

Попытка 5 — через 1 час

5

Попытка 6 — через 6 часов

6

Попытка 7+ — через 24 часа (далее интервал не растёт)

Максимум 8 попыток по умолчанию (оператор может настроить от 1 до 20). После исчерпания попыток доставка помечается как fail; такой webhook можно вручную переотправить через эндпоинт resend.
Один и тот же X-Event-Id приходит на все попытки доставки одного события. Обработайте событие ровно один раз: при повторе с уже виденным id отвечайте 200 без побочных эффектов. Иначе временный сбой на вашей стороне приведёт к двойной обработке оплаты.
Если для сайта включён режим 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’а с теми же заголовками и подписью, что и у боевых событий, но с особым типом X-Event-Type: webhook.test. Удобно проверить, что ваш endpoint доступен, принимает POST и корректно валидирует X-Signature. Повторов нет — ровно одна попытка. Тело тестового webhook’а, которое получит ваш callback_url:
Тело тестового webhook
Поля data:

POST /v1/public/webhooks/resend/{eventId}

Переотправляет ранее сгенерированный webhook по его X-Event-Id. Тело и URL берутся из лога доставки, подпись пересчитывается под текущий callback_secret. Повтор использует обычное расписание повторных попыток. Переотправлять можно только события своего сайта — доступ к чужим eventId невозможен (вернётся 404).
Поля data:

Частые вопросы

Почти всегда причина в том, что подпись считается не от сырого тела. Прочитайте тело как буфер/строку до парсинга JSON и считайте HMAC-SHA256 именно от этих байтов. Повторная сериализация распарсенного объекта меняет порядок ключей и пробелы — подпись не совпадёт. В Express используйте express.raw(), во Flask — request.get_data().
Проверьте по порядку: настроен ли callback_url для сайта; доступен ли ваш endpoint извне (или включён ли allow_private_hosts, если он во внутренней сети); не режут ли запрос ваш firewall/WAF. Затем вызовите POST /v1/public/webhooks/test — он поставит тестовую доставку и вернёт url и eventId, по которым видно, куда платформа пыталась доставить.
Это ожидаемо: при не-2xx ответе или тайм-ауте платформа повторяет доставку (до 8 раз по умолчанию), а одно событие может также прийти повторно после ручного resend. На все повторы один и тот же X-Event-Id. Дедуплицируйте по нему: при уже обработанном id отвечайте 200 без побочных эффектов.
10 секунд по умолчанию (оператор может задать 1–60 секунд). Если обработка дольше — примите webhook, поставьте задачу в свою очередь и сразу верните 200, а тяжёлую логику выполняйте асинхронно. Долгий ответ платформа считает неудачей и повторит доставку.
deposit.tx_detected — первая входящая транзакция замечена, но ещё не набрала нужных подтверждений (receivedAmount/txHash могут быть уже заполнены, но статус не финальный). deposit.finalized — депозит достиг requiredConfirmations; смотрите deposit.status (paid / paid_over / wrong_amount), чтобы понять исход. Зачислять средства клиенту стоит на deposit.finalized со статусом paid/paid_over, не на tx_detected.
Указанный eventId не найден среди событий вашего сайта. Переотправлять можно только свои события. Проверьте, что eventId взят из реально доставлявшегося ранее webhook’а (заголовок X-Event-Id или поле eventId тела) и принадлежит тому же сайту, чьим ключом подписан запрос.

Смежные страницы