ПлатежиВебхуки

Вебхуки (уведомления)

Когда статус платежа меняется на completed или failed, наша система автоматически отправляет POST-запрос на URL, указанный в настройках кассы. Это позволяет вашему серверу узнавать о результатах платежей в реальном времени.

МетодPOST
Content-Typeapplication/json
URLЗадаётся в настройках кассы (webhook_url)

Когда отправляется вебхук

completed

Платёж завершён — деньги получены, платёж успешно проведён

failed

Платёж отклонён — не удалось провести платёж

Заголовки запроса

Каждый вебхук содержит два заголовка:

ЗаголовокОписание
Content-TypeВсегда application/json
X-Webhook-SignatureHMAC-SHA256 подпись тела запроса. Используется для проверки подлинности

Тело запроса (Payload)

Пример тела запроса:

JSON
{"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_idstring (UUID)Уникальный идентификатор платежа в нашей системе
statusstringТекущий статус: completed или failed
amountfloatИсходная сумма, указанная при создании платежа
currencystringВалюта платежа (например, RUB, USDT)
pay_amountfloatФактическая сумма к оплате (с учётом комиссии)
fact_amountfloat / nullРеально полученная сумма. null, если платёж не завершён
product_idstring / nullВаш идентификатор заказа, переданный при создании платежа
descriptionstring / nullОписание платежа, переданное при создании
metadataobject / nullПроизвольные данные, переданные вами при создании платежа
idempotency_keystring / nullКлюч идемпотентности — для защиты от дублей платежей

Проверка подлинности (верификация подписи)

Обязательно проверяйте подпись каждого вебхука. Это гарантирует, что запрос отправлен нашей системой, а не злоумышленником.

Как формируется подпись

  1. Тело запроса (raw JSON) кодируется в строку.
  2. Вычисляется HMAC-SHA256 от этой строки с использованием секретного ключа кассы (secret_key).
  3. Результат (hex-строка) помещается в заголовок X-Webhook-Signature.

Формула

signature = HMAC-SHA256(тело_запроса, secret_key)

тело_запроса — сырая JSON-строка в точности как получена (без переформатирования).
secret_key — секретный ключ вашей кассы, доступен в личном кабинете.

Пример верификации на PHP

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

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

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 часов.

Защита от дублей

Мы можем отправить один и тот же вебхук более одного раза (например, при таймауте). Рекомендации:

1

Используйте payment_id как идемпотентный ключ

Если вы уже обработали платёж с таким ID — просто верните 200 OK, не повторяя бизнес-логику.

2

Проверяйте status

Обновляйте статус заказа только если он отличается от текущего.

PHP — защита от дублей
$paymentId = $data['payment_id'];$status = $data['status'];$existing = getOrder($paymentId);if ($existing && $existing['status'] === $status) {// Уже обработано — отвечаем 200http_response_code(200);exit;}// ОбрабатываемupdateOrder($paymentId, $status);

Полный пример обработчика (PHP)

webhook-handler.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';

Пошаговая интеграция

1

Создайте кассу в личном кабинете, получите secret_key

2

Укажите webhook_url в настройках кассы — URL на вашем сервере. URL должен быть публично доступен и возвращать HTTP 200 на GET-запрос.

3

Реализуйте обработчик: читает тело → проверяет подпись → декодирует JSON → обрабатывает статус → отвечает 200

4

Протестируйте, создав тестовый платёж через API

5

Убедитесь, что обработчик отвечает быстрее 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 указывает на рабочий эндпоинт.