Вебхуки связок
Подписанные уведомления о смене статуса рекуррентных связок
О каждой смене статуса связки мы отправляем подписанный POST-запрос. Списания — это заказы: их результат приходит обычными вебхуками заказов с X-Type: ORDER_UPDATE.
Адрес отправки уведомлений
Адрес определяется в таком порядке:
callbackUrlсвязки (передаётся вPOST /v1/recurring/bindings);callbackUrlзаказа первого платежа (для связки, сохранённой с платежом);callbackUrlиз настроек мерчанта;- если ничего не задано, уведомления по связке не отправляются.
Условия отправки уведомлений
Уведомление формируется при смене статуса связки: ACTIVE после подтверждения плательщиком, FAILED, SUSPENDED, DELETED. Начальный статус PENDING не отправляется: он возвращается при создании связки.
Для связки, сохранённой с первым платежом, уведомление ACTIVE обычно приходит прямо перед уведомлением заказа со статусом COMPLETED.
Дубли исключаются по сочетанию адреса, ID связки и статуса среди неотправленных уведомлений.
Формат уведомления
POST {callbackUrl}
Content-Type: application/json
X-Type: RECURRING_BINDING_UPDATE
X-Signature: HMAC-SHA512(тело запроса, API-токен) в hexТело
{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "ACTIVE",
"isTest": false,
"externalId": "sub-42",
"externalUserId": "user_987",
"merchantId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5e",
"purpose": "Ежемесячная подписка",
"initialOrderId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c",
"bankName": "Сбербанк",
"bankLogoUrl": null,
"createdAt": "2026-10-21T12:34:56.000Z",
"updatedAt": "2026-10-21T12:36:10.512Z",
"activatedAt": "2026-10-21T12:36:10.512Z",
"closedAt": null
}| Поле | Тип | Описание |
|---|---|---|
id | string (UUID) | ID связки |
status | string | Текущий статус, см. Статусы связки |
isTest | boolean | Тестовая связка |
externalId | string / null | ID связки в вашей системе |
externalUserId | string | ID плательщика в вашей системе |
merchantId | string (UUID) | Ваш ID мерчанта |
purpose | string / null | Описание подписки |
initialOrderId | string / null | Заказ, оплата которого создала связку |
bankName | string / null | Банк плательщика |
bankLogoUrl | string / null | Логотип банка плательщика |
createdAt | string | Время создания |
updatedAt | string | Время последнего изменения |
activatedAt | string / null | Когда связка впервые стала ACTIVE |
closedAt | string / null | Когда связка стала DELETED или FAILED |
Проверка подписи
Каждое уведомление содержит заголовок X-Signature: HMAC-SHA512 от сырого тела запроса в hex, ключ — ваш API-токен. Та же схема используется для вебхуков платежей, выплат и возвратов.
import crypto from "crypto";
// Ваш API-токен используется как секрет для HMAC
const apiToken = process.env.API_TOKEN;
app.post("/webhooks/payments", (req, res) => {
const signature = req.headers["x-signature"];
const body = JSON.stringify(req.body);
// Считаем HMAC-SHA512 от тела запроса и сравниваем в HEX
const expected = crypto.createHmac("sha512", apiToken).update(body).digest("hex");
if (expected !== signature) {
return res.status(403).send("Invalid signature");
}
// Подпись верна — обрабатываем уведомление идемпотентно
const data = req.body;
if (data.status === "COMPLETED") {
console.log(`Заказ ${data.externalId} оплачен`);
}
res.status(200).send("OK");
});Если один адрес получает уведомления разных типов, различайте их по заголовку X-Type: ORDER_UPDATE, PAYOUT_UPDATE, REFUND_UPDATE или RECURRING_BINDING_UPDATE.
Политика повторных отправок
| Параметр | Значение |
|---|---|
| Попытки | до 30 |
| Интервал | 30 с × 2^n, не больше 30 минут |
| Критерий успеха | HTTP-статус 200–399 |
Также действует circuit breaker: адрес, который стабильно отвечает ошибками, временно исключается из доставки.
Рекомендации по работе с webhook-уведомлениями
- Всегда отвечайте кодом HTTP
200, чтобы подтвердить получение. Если ваш сервер не отвечает, мы повторяем отправку уведомления. - Проверяйте заголовок
X-Signatureперед обработкой тела запроса. - Обрабатывайте уведомления идемпотентно: одно и то же уведомление может быть доставлено несколько раз.
- Сопоставляйте уведомление с вашими записями по
externalId, а не по сумме. - Обрабатывайте все возможные значения статусов, включая неуспешные.