Вместо того чтобы дёргать статус платежа вручную GET запросом, укажите ссылку для уведомлений (callback_url) в настройках проекта в личном кабинете. Когда платёж меняет статус, мы сами отправим на неё POST запрос с телом уведомления. Каждое уведомление снабжено цифровой подписью в поле signature.

События

Уведомления отправляются только по платежам и только при переходе в финальный статус:
Событие (event)ОбъектКогда
payment.paidОбъект платежаПлатёж перешёл в статус paid.
payment.failedОбъект платежаПлатёж перешёл в статус failed или expired.

Формат уведомления

POST {callback_url}
Content-Type: application/json
Тело JSON-конверт с типом, событием, подписью и снимком объекта платежа.
{
  "type": "webhook",
  "event": "payment.paid",
  "signature": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "object": {
    "id": "01jbx9q8h7m2k3n4p5r6s7t8v9",
    "status": "paid",
    "amount": "1500.00",
    "currency": "RUB",
    "description": "id:1042",
    "payload": { "order_id": 1042 },
    "created_at": "2026-05-29T13:30:00Z"
  }
}
Поля конверта
type
string
Всегда "webhook".
event
string
"payment.paid" или "payment.failed".
signature
string
Цифровая подпись уведомления, HMAC-SHA256 в hex (см. «Проверка подписи»).
object
object
Снимок платежа.
Поля object
id
string
Идентификатор платежа.
status
string
Статус платежа. "paid", "failed" или "expired".
amount
string
Сумма платежа в формате "D.DD".
currency
string
Валюта платежа.
description
string
Описание. Поле отсутствует, если описание не задано.
payload
object
Произвольные данные мерчанта, которые вы передали при создании платежа. Если данных не было, возвращается пустой объект {}.
created_at
string
Время создания платежа (ISO 8601, UTC).

Ответ на уведомление

Чтобы подтвердить, что вы получили уведомление, ответьте HTTP-кодом 2xx (например, 200). Всё, что находится в теле и заголовках ответа, игнорируется. Ответы с любыми другими кодами считаются неуспехом, и доставка будет повторяться (см. «Повторные доставки»):
  • Повторяются: 5xx, 408, 425, 429, таймаут и сетевые ошибки.
  • Не повторяются: прочие 4xx (400, 401, 403, 404, 410, 422, …) — считаются окончательным отказом.
На ответ у вас есть до 30 секунд на запрос. Заголовок Retry-After игнорируется.

Проверка подписи

Подпись в поле signature — это HMAC-SHA256 в нижнем регистре (hex, без префикса). Ключ — ваш API-ключ мерчанта (тот же, что и для запросов к API). Отдельного секрета для уведомлений нет. Подписывается не всё тело, а каноническая строка из четырёх значений, соединённых запятыми в фиксированном порядке:
{id},{status},{amount},{currency}
Например:
01jbx9q8h7m2k3n4p5r6s7t8v9,paid,1500.00,RUB
1

Соберите значения

Возьмите object.id, object.status, object.amount, object.currency.
2

Сформируйте строку

Соберите строку "{id},{status},{amount},{currency}".
3

Посчитайте HMAC

Посчитайте HMAC-SHA256(key = api_key, msg = строка) и переведите результат в hex.
4

Сравните

Сравните результат с полем signature в постоянном по времени сравнении.
import hmac
import hashlib

def verify(body: dict, api_key: str) -> bool:
    obj = body["object"]
    signed = f"{obj['id']},{obj['status']},{obj['amount']},{obj['currency']}"
    expected = hmac.new(
        api_key.encode("utf-8"),
        signed.encode("utf-8"),
        hashlib.sha256,
    ).hexdigest()
    return hmac.compare_digest(expected, body["signature"])

Повторные доставки

Если подтверждения не было, доставка повторяется по фиксированному расписанию (задержка перед следующей попыткой, в секундах):
10, 30, 60, 180, 300, 600, 1800, 3600, 10800, 21600, 43200, 86400, 86400
Итого до 14 попыток (первая + 13 повторов) в течение примерно 3 суток, после чего доставка прекращается.
Доставка гарантируется как минимум один раз (at-least-once): одно и то же уведомление может прийти несколько раз, поэтому обязательно обрабатывайте их идемпотентно.