Вебхуки
NordPay отправляет POST-запрос на ваш адрес, когда меняется статус платежа, подписки или выплаты. Так вы узнаёте об оплате сразу, без опроса API.
{
"id": "evt_7Qw2sK9nXc4M",
"object": "event",
"type": "payment.succeeded",
"created_at": "2026-11-08T12:03:42Z",
"livemode": false,
"data": {
"object": {
"id": "pay_2Kq8sD1fVxJ7mN4p",
"object": "payment",
"status": "succeeded",
"amount": 499000,
"amount_refunded": 0,
"currency": "RUB",
"fee": 17465,
"net": 481535,
"method": "sbp",
"description": "Заказ №1042",
"order_id": "1042",
"metadata": {
"cart_id": "c_77"
},
"customer": {
"email": "buyer@example.com"
},
"confirmation": {
"type": "redirect",
"url": "https://api.nordpay.pro/checkout/pay_2Kq8sD1fVxJ7mN4p"
},
"sbp": {
"qr_payload": "https://api.nordpay.pro/checkout/pay_2Kq8sD1fVxJ7mN4p",
"expires_at": "2026-11-08T12:30:00Z"
},
"crypto": null,
"crypto_network": null,
"card": null,
"failure": null,
"livemode": false,
"created_at": "2026-11-08T12:00:00Z",
"paid_at": "2026-11-08T12:03:41Z",
"expires_at": "2026-11-08T12:30:00Z"
}
}
}Адрес для уведомлений
Добавьте адрес в кабинете, в разделе «Вебхуки и API-ключи», или через API. Укажите, какие события присылать. В ответе — secret для проверки подписи; он показывается один раз.
- Адрес должен быть доступен из интернета по HTTPS.
- Для тестового и боевого режимов заводятся разные адреса и секреты. Список —
GET /v1/webhook_endpoints, удаление —DELETE /v1/webhook_endpoints/:id. - Из кабинета можно отправить тестовое событие и посмотреть журнал доставок: код ответа, время, тело запроса.
curl -X POST https://api.nordpay.pro/v1/webhook_endpoints \
-H "Authorization: Bearer $NORDPAY_SECRET_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://shop.example/webhooks/nordpay",
"events": [
"payment.succeeded",
"payment.failed",
"payment.refunded"
]
}'{
"id": "we_4Rk8pT1mZs6N",
"object": "webhook_endpoint",
"url": "https://shop.example/webhooks/nordpay",
"events": [
"payment.succeeded",
"payment.failed",
"payment.refunded"
],
"status": "enabled",
"secret": "9c1f4e…b7a2",
"created_at": "2026-11-08T12:00:00Z"
}Формат события
id— уникальный идентификатор события с префиксомevt_. Одно и то же событие может прийти повторно — используйтеid, чтобы не обработать его дважды.type— тип события, см. таблицу ниже.data.object— объект целиком в состоянии на момент события: платёж, подписка или выплата.livemode— режим, в котором произошло событие.
Порядок доставки не гарантирован: если важна последовательность, сравнивайте статус и время в объекте или запрашивайте актуальное состояние через API.
POST /webhooks/nordpay HTTP/1.1
Host: shop.example
Content-Type: application/json
NordPay-Signature: t=1762603422,v1=5f0c2e…d41a
{"id":"evt_7Qw2sK9nXc4M","object":"event","type":"payment.succeeded",…}Типы событий
| Тип | Когда приходит |
|---|---|
payment.succeeded | Платёж оплачен, деньги зачислены в баланс |
payment.failed | Платёж отклонён, причина — в failure |
payment.canceled | Платёж отменён до оплаты |
payment.refunded | По платежу проведён полный или частичный возврат |
subscription.created | Подписка создана |
subscription.renewed | Очередное списание по подписке прошло успешно |
subscription.payment_failed | Очередное списание не прошло |
subscription.canceled | Подписка отменена |
payout.paid | Выплата отправлена на ваши реквизиты |
payout.rejected | Выплата отклонена |
{
"id": "evt_2Bn7rD5kWq8Y",
"object": "event",
"type": "subscription.payment_failed",
"livemode": false,
"created_at": "2026-12-08T12:00:07Z",
"data": {
"object": {
"id": "sub_3Lm9xR2pVa7Q",
"object": "subscription",
"status": "past_due"
}
}
}Подпись
Каждый запрос подписан. В заголовке NordPay-Signature два значения: t — время отправки в секундах Unix и v1 — подпись в hex.
v1 = HMAC_SHA256(webhook_secret, "<t>.<сырое тело запроса>")- Возьмите тело запроса как есть, до разбора JSON и любых преобразований.
- Склейте строку
t, точку и тело, посчитайте HMAC-SHA256 секретом адреса. - Сравните с
v1функцией сравнения за постоянное время. - Отклоните запрос, если
tотличается от текущего времени больше чем на 5 минут, — это защита от повторной отправки перехваченного запроса.
import crypto from 'node:crypto';
export function verifyNordPay(rawBody, header, secret, toleranceSec = 300) {
const parts = Object.fromEntries(
String(header || '').split(',').map((kv) => kv.trim().split('=')),
);
const t = Number(parts.t);
if (!t || !parts.v1) return false;
if (Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
const expected = crypto
.createHmac('sha256', secret)
.update(`${t}.${rawBody}`)
.digest('hex');
return expected.length === parts.v1.length &&
crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(parts.v1));
}Повторы
Доставка считается успешной, если ваш сервер ответил кодом 2xx в течение 10 секунд. Иначе NordPay повторит отправку по расписанию:
- через 1 минута
- через 5 минут
- через 30 минут
- через 2 часа
- через 6 часов
- через 24 часа
Все попытки с кодом ответа и временем видны в журнале доставок в кабинете. Если ваш сервер долго был недоступен, сверьте статусы платежей через список платежей.
app.post('/webhooks/nordpay', express.raw({ type: 'application/json' }), async (req, res) => {
if (!verifyNordPay(req.body, req.get('NordPay-Signature'), process.env.NORDPAY_WEBHOOK_SECRET)) {
return res.sendStatus(400);
}
const event = JSON.parse(req.body);
const isNew = await db.events.insertIfAbsent(event.id);
if (isNew) await queue.push('nordpay-event', event);
res.sendStatus(200);
});