Вебхуки (уведомления)
Когда статус платежа меняется на completed или failed, наша система автоматически отправляет POST-запрос на URL, указанный в настройках кассы. Это позволяет вашему серверу узнавать о результатах платежей в реальном времени.
| Метод | POST |
| Content-Type | application/json |
| URL | Задаётся в настройках кассы (webhook_url) |
Когда отправляется вебхук
Платёж завершён — деньги получены, платёж успешно проведён
Платёж отклонён — не удалось провести платёж
Заголовки запроса
Каждый вебхук содержит два заголовка:
| Заголовок | Описание |
|---|---|
Content-Type | Всегда application/json |
X-Webhook-Signature | HMAC-SHA256 подпись тела запроса. Используется для проверки подлинности |
Тело запроса (Payload)
Пример тела запроса:
{"payment_id": "0191a2b3-c4d5-7e8f-9a0b-1c2d3e4f5a6b","status": "completed","amount": 1000.00,"currency": "RUB","pay_amount": 1052.63,"fact_amount": 1052.63,"product_id": "order-12345","description": "Оплата заказа №12345","metadata": {"user_id": "42","cart_items": ["SKU001", "SKU002"]},"idempotency_key": "order-12345-pay-1"}Описание полей
| Поле | Тип | Описание |
|---|---|---|
payment_id | string (UUID) | Уникальный идентификатор платежа в нашей системе |
status | string | Текущий статус: completed или failed |
amount | float | Исходная сумма, указанная при создании платежа |
currency | string | Валюта платежа (например, RUB, USDT) |
pay_amount | float | Фактическая сумма к оплате (с учётом комиссии) |
fact_amount | float / null | Реально полученная сумма. null, если платёж не завершён |
product_id | string / null | Ваш идентификатор заказа, переданный при создании платежа |
description | string / null | Описание платежа, переданное при создании |
metadata | object / null | Произвольные данные, переданные вами при создании платежа |
idempotency_key | string / null | Ключ идемпотентности — для защиты от дублей платежей |
Проверка подлинности (верификация подписи)
Обязательно проверяйте подпись каждого вебхука. Это гарантирует, что запрос отправлен нашей системой, а не злоумышленником.
Как формируется подпись
- Тело запроса (raw JSON) кодируется в строку.
- Вычисляется HMAC-SHA256 от этой строки с использованием секретного ключа кассы (
secret_key). - Результат (hex-строка) помещается в заголовок
X-Webhook-Signature.
Формула
signature = HMAC-SHA256(тело_запроса, secret_key)тело_запроса — сырая JSON-строка в точности как получена (без переформатирования).
secret_key — секретный ключ вашей кассы, доступен в личном кабинете.
Пример верификации на PHP
$secretKey = 'ваш_secret_key';$payload = file_get_contents('php://input');$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];$expected = hash_hmac('sha256', $payload, $secretKey);if (hash_equals($expected, $signature)) {// Подпись верна — обрабатываем$data = json_decode($payload, true);} else {http_response_code(403);exit;}Пример верификации на Python
import hmac, hashlib
secret_key = 'ваш_secret_key'payload = request.body # Django: request.body / Flask: request.get_data()signature = request.headers['X-Webhook-Signature']expected = hmac.new(secret_key.encode('utf-8'),payload,hashlib.sha256).hexdigest()if hmac.compare_digest(expected, signature):data = json.loads(payload)# обрабатываем...Пример верификации на Node.js
const crypto = require('crypto');const secretKey = 'ваш_secret_key';const payload = request.rawBody; // сырая строка, не JSON!const signature = request.headers['x-webhook-signature'];const expected = crypto
.createHmac('sha256', secretKey).update(payload).digest('hex');if (expected === signature) {const data = JSON.parse(payload);// обрабатываем...}Как должен отвечать ваш сервер
| Ответ | Результат |
|---|---|
| HTTP 200 – 299 | Вебхук считается успешно доставленным |
| Другой код / таймаут | Вебхук считается не доставленным, будет повторная попытка |
Ваш сервер должен ответить 200 OK в течение 10 секунд. Не выполняйте тяжёлую логику синхронно — примите запрос, верните 200, обработку делайте в фоне.
Повторные попытки доставки
Если ваш сервер не ответил 200 OK, система будет повторять отправку:
| Попытка | Задержка |
|---|---|
| 1 | Сразу |
| 2 | Через 30 секунд |
| 3 | Через 2 минуты |
| 4 | Через 10 минут |
| 5 | Через 30 минут |
Максимум 5 попыток в течение 24 часов.
Защита от дублей
Мы можем отправить один и тот же вебхук более одного раза (например, при таймауте). Рекомендации:
Используйте payment_id как идемпотентный ключ
Если вы уже обработали платёж с таким ID — просто верните 200 OK, не повторяя бизнес-логику.
Проверяйте status
Обновляйте статус заказа только если он отличается от текущего.
$paymentId = $data['payment_id'];$status = $data['status'];$existing = getOrder($paymentId);if ($existing && $existing['status'] === $status) {// Уже обработано — отвечаем 200http_response_code(200);exit;}// ОбрабатываемupdateOrder($paymentId, $status);Полный пример обработчика (PHP)
<?php$secretKey = 'ваш_secret_key';// 1. Сырое тело запроса$payload = file_get_contents('php://input');// 2. Проверяем подпись$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'] ?? '';$expected = hash_hmac('sha256', $payload, $secretKey);if (!hash_equals($expected, $signature)) {http_response_code(403);exit('Invalid signature');}// 3. Декодируем JSON$data = json_decode($payload, true);// 4. Обрабатываем статусswitch ($data['status']) {case'completed':completeOrder($data['payment_id'], $data['fact_amount']);break;case'failed':failOrder($data['payment_id']);break;}// 5. Отвечаем 200http_response_code(200);echo'OK';Пошаговая интеграция
Создайте кассу в личном кабинете, получите secret_key
Укажите webhook_url в настройках кассы — URL на вашем сервере. URL должен быть публично доступен и возвращать HTTP 200 на GET-запрос.
Реализуйте обработчик: читает тело → проверяет подпись → декодирует JSON → обрабатывает статус → отвечает 200
Протестируйте, создав тестовый платёж через API
Убедитесь, что обработчик отвечает быстрее 10 секунд
Частые вопросы
Что если мой сервер был недоступен?
Вебхук будет отправлен повторно до 5 раз с нарастающей задержкой. Также вы всегда можете проверить текущий статус платежа через API.
Можно ли указать другой URL для конкретного платежа?
Да, при создании платежа можно указать webhook_url — он имеет приоритет над URL из настроек кассы.
Что хранить в metadata?
Любые данные для связи платежа с вашим заказом: ID пользователя, корзину, callback-URL. Эти данные возвращаются без изменений.
Насколько безопасна подпись?
HMAC-SHA256 — промышленный стандарт. Секретный ключ известен только вам и нашей системе. Без ключа подделать подпись невозможно.
Почему мой webhook_url отклонён?
При сохранении webhook_url в настройках кассы или указании его при создании платежа система проверяет, что URL:
- Не указывает на локальный или частный адрес (localhost, 127.0.0.1, 192.168.x.x и т.д.)
- Доступен из интернета — сервер должен вернуть HTTP 200 на GET-запрос
Убедитесь, что ваш сервер запущен и доступен, а URL указывает на рабочий эндпоинт.
