Webhook-уведомления по возвратам
Подписанные уведомления об изменении статуса возвратов
При каждом изменении статуса возврата после его создания мы отправляем подписанный POST-запрос. Опрос GET /v1/refunds/{refundId} — резервный способ, а не замена.
Адрес отправки уведомлений
Адрес определяется в следующем порядке:
callbackUrl, переданный в теле запросаPOST /v1/refunds: применяется только к этому возврату;callbackUrlзаказа, по которому сделан возврат;callbackUrlиз настроек мерчанта;- если не задано ни одно из значений, уведомления по возврату не отправляются.
Требования к callbackUrl
| Требование | Значение |
|---|---|
| Схема | http или https |
| Длина | до 2048 символов |
| Валидация | некорректный URL → 400 Validation failed |
Условия отправки уведомлений
Уведомление формируется при каждом изменении статуса после создания возврата. Начальный статус (MANUAL, PENDING или FAILED) не отправляется: он возвращается в ответе на POST /v1/refunds.
Когда возврат переходит в COMPLETED, заказ переходит в статус REFUND, поэтому приходит и уведомление по заказу с X-Type: ORDER_UPDATE. Если платформа позже переведёт завершённый возврат в другой статус, заказ вернётся в COMPLETED, и вы получите уведомления об обоих изменениях.
Ваш менеджер также может повторно отправить уведомление вручную. Дублирующиеся записи исключаются по совокупности адреса, ID возврата и статуса среди неотправленных уведомлений.
Формат уведомления
POST {callbackUrl}
Content-Type: application/json
X-Type: REFUND_UPDATE
X-Signature: HMAC-SHA512(тело запроса, API-токен) в формате hexТело
{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"orderId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c",
"orderExternalId": "order_1234",
"externalId": "refund-123",
"status": "COMPLETED",
"amount": "1000.5",
"currency": "RUB",
"debitAmount": "1000.5",
"debitCurrency": "RUB",
"reason": "Запрос покупателя",
"merchantId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5e",
"createdAt": "2026-09-23T12:34:56.000Z",
"updatedAt": "2026-09-23T12:41:03.512Z",
"completedAt": "2026-09-23T12:41:03.512Z"
}| Поле | Тип | Описание |
|---|---|---|
id | string (UUID) | ID возврата |
orderId | string (UUID) | ID заказа, по которому сделан возврат |
orderExternalId | string / null | externalId заказа |
externalId | string / null | ID возврата в вашей системе |
status | string | Текущий статус, см. Статусы возвратов |
amount | string | Сумма, возвращаемая плательщику, в валюте заказа |
currency | string | Валюта заказа |
debitAmount | string | Сумма списания с вашего баланса |
debitCurrency | string | Валюта баланса, с которого списывается возврат |
reason | string / null | Причина возврата |
merchantId | string (UUID) | ID вашего мерчанта |
createdAt | string | Время создания |
updatedAt | string | Время последнего обновления |
completedAt | string / null | Время завершения |
Проверка подписи
Каждое уведомление содержит заголовок X-Signature: HMAC-SHA512-хеш исходного тела запроса в формате hex, вычисленный с вашим API-токеном в качестве секрета. Та же схема используется для webhook-уведомлений по платежам и выплатам.
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");
});Если один endpoint получает уведомления разных видов, различайте их по заголовку X-Type: ORDER_UPDATE, PAYOUT_UPDATE или REFUND_UPDATE.
Политика повторных отправок
| Параметр | Значение |
|---|---|
| Количество попыток | до 30 |
| Интервал | 30s × 2^n, но не более 30 минут |
| Критерий успеха | HTTP-статус 200–399 |
Дополнительно применяется circuit breaker: адрес, стабильно возвращающий ошибки, временно исключается из доставки.
Рекомендации по работе с webhook-уведомлениями
- Всегда отвечайте кодом HTTP
200, чтобы подтвердить получение. Если ваш сервер не отвечает, мы повторяем отправку уведомления. - Проверяйте заголовок
X-Signatureперед обработкой тела запроса. - Обрабатывайте уведомления идемпотентно: одно и то же уведомление может быть доставлено несколько раз.
- Сопоставляйте уведомление с вашими записями по
externalId, а не по сумме. - Обрабатывайте все возможные значения статусов, включая неуспешные.