Binding webhooks
Signed notifications about recurring binding status changes
We send a signed POST request on every binding status change. Charges are orders: their results come in the usual order webhooks with X-Type: ORDER_UPDATE.
Where notifications are sent
The address is resolved in this order:
callbackUrlof the binding (passed toPOST /v1/recurring/bindings);callbackUrlof the first payment order (for a binding saved with a payment);callbackUrlfrom your merchant settings;- if none is set, no notifications are sent for the binding.
When notifications are sent
A notification is generated when the binding status changes: ACTIVE after the payer confirms it, FAILED, SUSPENDED, DELETED. The initial PENDING status is not sent: it is returned when the binding is created.
For a binding saved with the first payment, the ACTIVE notification usually arrives right before the order notification with the COMPLETED status.
Duplicate records are excluded by the combination of address, binding ID and status among unsent notifications.
Notification format
POST {callbackUrl}
Content-Type: application/json
X-Type: RECURRING_BINDING_UPDATE
X-Signature: HMAC-SHA512(request body, API token) in hexBody
{
"id": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5b",
"status": "ACTIVE",
"isTest": false,
"externalId": "sub-42",
"externalUserId": "user_987",
"merchantId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5e",
"purpose": "Monthly subscription",
"initialOrderId": "0199a1b2-c3d4-7e5f-8a9b-0c1d2e3f4a5c",
"bankName": "Sberbank",
"bankLogoUrl": null,
"createdAt": "2026-10-21T12:34:56.000Z",
"updatedAt": "2026-10-21T12:36:10.512Z",
"activatedAt": "2026-10-21T12:36:10.512Z",
"closedAt": null
}| Field | Type | Description |
|---|---|---|
id | string (UUID) | Binding ID |
status | string | Current status, see Binding statuses |
isTest | boolean | Test binding |
externalId | string / null | Binding ID in your system |
externalUserId | string | Payer ID in your system |
merchantId | string (UUID) | Your merchant ID |
purpose | string / null | Subscription description |
initialOrderId | string / null | Order whose payment created the binding |
bankName | string / null | Payer's bank |
bankLogoUrl | string / null | Payer's bank logo |
createdAt | string | Creation time |
updatedAt | string | Last update time |
activatedAt | string / null | When the binding first became ACTIVE |
closedAt | string / null | When the binding became DELETED or FAILED |
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, payout and refund 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, REFUND_UPDATE or RECURRING_BINDING_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.