Payment Docs
Payouts

Payout webhooks

Signed notifications about payout status changes

We send a signed POST request on every payout status change. Polling GET /v1/payouts/{payoutId} 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/payouts: applies to this payout only;
  2. callbackUrl from your merchant settings;
  3. if neither is set, no notifications are sent for the payout.

The address is fixed when the payout is created. Changing the merchant settings later does not move notifications of an already created payout to the new address.

callbackUrl requirements

RequirementValue
Schemehttp or https
Lengthup to 2048 characters
Validationinvalid URL → 400 Validation failed

The field is returned in the payout object. null means the merchant settings are used.

When notifications are sent

A notification is generated on every status change with three exceptions:

  • CREATED: no notification;
  • COMPLETED: the notification is queued within the payout completion transaction, once the final fee is known;
  • a manual change of a COMPLETED payout because of an error on the bank side: the notification is sent on request, ask your manager.

Your manager can also resend a notification manually. Duplicate records are excluded by the combination of address, payout ID and status among unsent notifications.

Notification format

POST {callbackUrl}
Content-Type: application/json
X-Type: PAYOUT_UPDATE
X-Signature: HMAC-SHA512(request body, API token) in hex

Body

{
  "id": "0198c3f1-2a4b-7c3d-9e0f-1a2b3c4d5e6f",
  "status": "COMPLETED",
  "amount": "1000.5",
  "fee": "60",
  "currency": "RUB",
  "type": "C2C",
  "note": null,
  "externalId": "payout-123",
  "merchantId": "0198c3f1-1111-7222-8333-444455556666",
  "settlementCurrency": "USD",
  "settlementAmount": "11.28",
  "createdAt": "2026-07-29T10:00:00.000Z",
  "completedAt": "2026-07-29T10:04:12.317Z"
}
FieldTypeDescription
idstring (UUID)Payout ID
statusstringCurrent status, see Payout statuses
amountstringPayout amount
feestringFee charged for the payout
currencystringPayout currency
typestringPayment type
notestring / nullPayout note
externalIdstring / nullPayout ID in your system
merchantIdstring (UUID)Your merchant ID
settlementCurrencystring / nullSettlement currency
settlementAmountstring / nullAmount in the settlement currency
createdAtstringCreation time
completedAtstring / nullCompletion time

callbackUrl is not included in the body: it defines the delivery address and is not part of the payout state.

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 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 both payment and payout notifications, distinguish them by the X-Type: PAYOUT_UPDATE header.

Retry policy

ParameterValue
Attemptsup to 30
Interval30s × 2^n, capped at 30 minutes
Success criterionHTTP status 200399

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