Webhooks
Webhooks let you receive real-time notifications about payment status changes.
Setup
When creating a payment, specify:
notify_url— URL to receive notificationsnotification_token— token for validation (optional)
Webhook Headers
| Header | Description |
|---|---|
Content-Type | application/json |
X-Webhook-Event | Event type (payment.status_changed) |
X-Signature | HMAC-SHA256 signature of the request body |
X-Notification-Token | Your token from notification_token (if it was provided) |
Validation via Notification Token
If you passed a notification_token when creating the payment, it is returned in the X-Notification-Token header. Compare it with the expected value for quick validation without computing a signature.
Webhook Format
{
"event": "payment.status_changed",
"paymentId": "550e8400-e29b-41d4-a716-446655440000",
"ext_id": "order_12345",
"status": "COMPLETED",
"amount": 5000,
"currency": "RUB",
"timestamp": "2026-01-30T12:22:00Z"
}
Events
| Event | Status | Description |
|---|---|---|
payment.status_changed | COMPLETED | Payment successfully completed |
payment.status_changed | CANCELLED | Payment cancelled |
payment.status_changed | COMPLETED_APPEAL | Dispute approved |
payment.status_changed | CANCELLED_APPEAL | Dispute rejected |
payment.status_changed | DISPUTE | Dispute opened on the payment |
Signature Verification
The X-Signature header contains the HMAC-SHA256 signature of the request body (hex).
JavaScript
const crypto = require('crypto');
function verifyWebhook(body, signature, secretKey) {
const expectedSignature = crypto
.createHmac('sha256', secretKey)
.update(JSON.stringify(body))
.digest('hex');
return signature === expectedSignature;
}
app.post('/webhook', (req, res) => {
// Option 1: Validate via notification_token (fast)
const token = req.headers['x-notification-token'];
if (token && token === expectedToken) {
// Validation passed
}
// Option 2: Validate via signature (reliable)
const signature = req.headers['x-signature'];
if (!verifyWebhook(req.body, signature, secretKey)) {
return res.status(401).send('Invalid signature');
}
const { event, paymentId, status } = req.body;
console.log(`${event}: Payment ${paymentId} is ${status}`);
res.status(200).send('OK');
});
Python
import hmac
import hashlib
import json
def verify_webhook(body, signature, secret_key):
expected_signature = hmac.new(
secret_key.encode(),
json.dumps(body).encode(),
hashlib.sha256
).hexdigest()
return signature == expected_signature
PHP
function verifyWebhook($body, $signature, $secretKey) {
$expectedSignature = hash_hmac(
'sha256',
json_encode($body),
$secretKey
);
return hash_equals($expectedSignature, $signature);
}
Server Requirements
- HTTP 200 OK — a webhook is considered delivered when HTTP 200 is received
- Timeout — the response must be received within 10 seconds
- Idempotency — handle duplicate deliveries correctly
Retry Policy
On failed delivery, the system will retry:
| Attempt | Delay |
|---|---|
| 1 | Immediately |
| 2 | 60 seconds |
| 3 | 60 seconds |
After 3 failed attempts, the webhook is marked as undelivered.