Вебхуки
Адрес вебхука магазина, подпись, повторы и порядок доставки
Вебхуки API v1 описаны в разделе API v1.
Вебхук приходит на каждую смену статуса заявки. Тело — заявка целиком, точно как в ответе
GET /api/v2/invoices/{invoice_id}. На создание заявки вебхука нет: заявку вы получили в ответе.
Настроить
Адрес вебхука один на магазин.
curl -X PUT https://api.parallel-pay.com/api/v2/webhook \
-H "Authorization: Bearer $PARALLEL_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/webhooks/parallel"
}'Ответ — адрес и ключ подписи:
{
"url": "https://example.com/webhooks/parallel",
"secret": "8f14e45fceea167a5a36dedd4bea2543"
}Сохраните secret: им вы проверяете подпись. Ключ создаётся при первой настройке.
Тело запроса:
Поле
Тип
Ответ:
Поле
Тип
Посмотреть настройку
Ответ — тот же объект url и secret. До первого PUT:
HTTP Status: 404 Not Found
{
"error": {
"code": "WEBHOOK_NOT_SET",
"message": "Вебхук ещё не настроен"
}
}Перевыпустить ключ
curl -X POST https://api.parallel-pay.com/api/v2/webhook/secret \
-H "Authorization: Bearer $PARALLEL_TOKEN"Ответ — тот же объект с новым secret. Если вебхук ещё не настроен — 404 WEBHOOK_NOT_SET.
Пока вебхук не настроен, вебхуки не отправляются. Состояние заявки всегда можно узнать запросом
GET /api/v2/invoices/{invoice_id}.
Запрос к вам
POST https://example.com/webhooks/parallel
Content-Type: application/json
X-Webhook-Signature: 3f4c1f0e8a3b4c2b9a1d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b
X-Webhook-Delivery-Id: 7c9e6679-7425-40de-944b-e07fc1f90ae7| Заголовок | Что в нём |
|---|---|
X-Webhook-Signature | HMAC-SHA256 сырого тела запроса ключом secret, в hex |
X-Webhook-Delivery-Id | Номер доставки. У повтора того же вебхука — тот же |
Тело — заявка. Пример — заявка оплачена:
{
"internal_id": "order-1001",
"invoice_id": "cm3k8x7y80001z8j4k5m6n7o8",
"direction": "IN",
"created_at": "2026-11-09T12:00:00+03:00",
"updated_at": "2026-11-09T12:07:00+03:00",
"amount": "1000",
"currency": "RUB",
"deal_rate": "95.5",
"requisites": {
"bank": "sberbank",
"bank_name": "Сбербанк",
"full_name": "Ivan I.",
"method": "SBP",
"phone_number": "+79001234567"
},
"attachments": [
{
"attachment_id": "cm3k9b2c40002z8j4q1r2s3t4",
"invoice_id": "cm3k8x7y80001z8j4k5m6n7o8",
"type": "PDF",
"file_name": "receipt.pdf",
"file_url": "https://files.parallel-pay.com/attachments/cm3k9b2c40002z8j4q1r2s3t4/receipt.pdf",
"received_at": "2026-11-09T12:05:00+03:00",
"status": "PASSED",
"checked_at": "2026-11-09T12:07:00+03:00",
"transfer": {
"amount": "1000",
"at": "2026-11-09T12:04:00+03:00",
"bank_status": "Успешно",
"operation_id": "C4213110902474567",
"from": {
"bank": "tbank",
"name": "Пётр П."
},
"to": {
"method": "SBP",
"bank": "sberbank",
"name": "Иван И.",
"phone_number": "+79001234567",
"card_number": null
}
}
}
],
"receipt_delivery": "UPLOAD",
"status": "PAID",
"paid_amount": "1000",
"paid_at": "2026-11-09T12:07:00+03:00"
}Доставка
- Вебхук доставлен, если вы ответили любым кодом 2xx.
- Иначе повторяем через 1 минуту, 5 минут, 30 минут, 2 часа, 6 часов и 12 часов. Повтор приходит с тем же
X-Webhook-Delivery-Id: если вы его уже обработали, просто ответьте 2xx. - После последнего повтора вебхук больше не отправляем. Состояние заявки всегда можно узнать запросом
GET /api/v2/invoices/{invoice_id}. - По одной заявке вебхуки приходят строго по порядку. Если сравниваете состояния сами — по
updated_at.
Проверка подписи
Считайте HMAC-SHA256 от сырого тела запроса — байтов, как они пришли, до разбора JSON. Ключ — secret из
настройки вебхука, результат — в hex. Сравнивайте с X-Webhook-Signature за постоянное время.
import crypto from "node:crypto";
import express from "express";
const app = express();
const secret = process.env.PARALLEL_WEBHOOK_SECRET;
// express.raw: подпись считается от сырого тела, а не от разобранного JSON
app.post("/webhooks/parallel", express.raw({ type: "*/*" }), (req, res) => {
const expected = crypto.createHmac("sha256", secret).update(req.body).digest("hex");
const signature = req.get("X-Webhook-Signature") ?? "";
if (
signature.length !== expected.length ||
!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))
) {
return res.status(401).send("invalid signature");
}
const invoice = JSON.parse(req.body.toString("utf8"));
handleInvoice(invoice, req.get("X-Webhook-Delivery-Id"));
res.sendStatus(200);
});
app.listen(3000);Проверьте свой код на этом примере. Тело — ровно эта строка, без пробелов и переноса в конце:
{"invoice_id":"cm3k8x7y80001z8j4k5m6n7o8","status":"PAID"}С ключом test_secret подпись — a43227d9daf9857e9af9788b204c1881cd6eb5186c31667bd491936ed17d7d6e.
Что делать по статусу
status | Что делать |
|---|---|
REVIEW | Ждать: мы проверяем файлы до review_until |
PROOF_REQUIRED | До proof_until загрузить видео оплаты или взять ФО |
PAID | Зачислить клиенту paid_amount, не amount. После спора PAID приходит ещё раз с новым paid_amount |
DISPUTE | Зачтённые USDT заморожены, ждать решения спора |
REVERSED | Зачисление отменено: отменить его и у себя |
EXPIRED, CANCELED | Закрыть заказ без оплаты |