Payment Docs
Refunds

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:

  1. callbackUrl passed in the body of POST /v1/refunds: applies to this refund only;
  2. callbackUrl of the refunded order;
  3. callbackUrl from your merchant settings;
  4. if none is set, no notifications are sent for the refund.

callbackUrl requirements

RequirementValue
Schemehttp or https
Lengthup to 2048 characters
Validationinvalid 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 hex

Body

{
  "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"
}
FieldTypeDescription
idstring (UUID)Refund ID
orderIdstring (UUID)ID of the refunded order
orderExternalIdstring / nullexternalId of the refunded order
externalIdstring / nullRefund ID in your system
statusstringCurrent status, see Refund statuses
amountstringAmount returned to the payer, in the order currency
currencystringOrder currency
debitAmountstringAmount taken from your balance
debitCurrencystringCurrency of the balance the refund is taken from
reasonstring / nullRefund reason
merchantIdstring (UUID)Your merchant ID
createdAtstringCreation time
updatedAtstringLast update time
completedAtstring / nullCompletion 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

ParameterValue
Attemptsup to 30
Interval30s × 2^n, capped at 30 minutes
Success criterionHTTP 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 200 to confirm receipt. If your server does not respond, we retry the notification.
  • Verify the X-Signature header 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.

See also

On this page