Refund webhooks
Signed notifications about refund status changes
We send a signed POST request on every refund status change after creation. Polling GET /v1/refunds/{refundId} is a fallback, not a replacement.
Where notifications are sent
The address is resolved in this order:
callbackUrlpassed in the body ofPOST /v1/refunds: applies to this refund only;callbackUrlof the refunded order;callbackUrlfrom your merchant settings;- if none is set, no notifications are sent for the refund.
callbackUrl requirements
| Requirement | Value |
|---|---|
| Scheme | http or https |
| Length | up to 2048 characters |
| Validation | invalid URL → 400 Validation failed |
When notifications are sent
A notification is generated on every status change after the refund is created. The initial status (MANUAL, PENDING or FAILED) is not sent: it is returned in the response to POST /v1/refunds.
When a refund moves to COMPLETED, the order moves to REFUND, so you also receive the order notification with X-Type: ORDER_UPDATE. If the platform later moves a completed refund to another status, the order returns to COMPLETED and you are notified about both changes.
Your manager can also resend a notification manually. Duplicate records are excluded by the combination of address, refund ID and status among unsent notifications.
Notification format
POST {callbackUrl}
Content-Type: application/json
X-Type: REFUND_UPDATE
X-Signature: HMAC-SHA512(request body, API token) in hexBody
{
"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": "Customer request",
"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"
}| Field | Type | Description |
|---|---|---|
id | string (UUID) | Refund ID |
orderId | string (UUID) | ID of the refunded order |
orderExternalId | string / null | externalId of the refunded order |
externalId | string / null | Refund ID in your system |
status | string | Current status, see Refund statuses |
amount | string | Amount returned to the payer, in the order currency |
currency | string | Order currency |
debitAmount | string | Amount taken from your balance |
debitCurrency | string | Currency of the balance the refund is taken from |
reason | string / null | Refund reason |
merchantId | string (UUID) | Your merchant ID |
createdAt | string | Creation time |
updatedAt | string | Last update time |
completedAt | string / null | Completion time |
Verify the signature
Every notification carries the X-Signature header: an HMAC-SHA512 hash of the raw request body in hex, computed with your API token as the secret. The same scheme is used for payment and payout webhooks.
import crypto from "crypto";
// Your API token is the HMAC secret
const apiToken = process.env.API_TOKEN;
app.post("/webhooks/payments", (req, res) => {
const signature = req.headers["x-signature"];
const body = JSON.stringify(req.body);
// Calculate HMAC-SHA512 of the raw request body and compare in hex
const expected = crypto.createHmac("sha512", apiToken).update(body).digest("hex");
if (expected !== signature) {
return res.status(403).send("Invalid signature");
}
// Signature is valid — process the notification idempotently
const data = req.body;
if (data.status === "COMPLETED") {
console.log(`Order ${data.externalId} has been paid`);
}
res.status(200).send("OK");
});If one endpoint receives several kinds of notifications, distinguish them by the X-Type header: ORDER_UPDATE, PAYOUT_UPDATE or REFUND_UPDATE.
Retry policy
| Parameter | Value |
|---|---|
| Attempts | up to 30 |
| Interval | 30s × 2^n, capped at 30 minutes |
| Success criterion | HTTP status 200–399 |
A circuit breaker is also applied: an address that consistently fails is temporarily excluded from delivery.
Webhook recommendations
- Always respond with HTTP
200to confirm receipt. If your server does not respond, we retry the notification. - Verify the
X-Signatureheader before processing the body. - Process notifications idempotently: the same notification may be delivered more than once.
- Match the notification to your records by
externalId, not by amount. - Handle every possible status value, including the unsuccessful ones.