Протокол

Вебхуки

При каждом изменении статуса на notify_url (или webhook сайта) уходит POST:

{
  "webhook_id": "9127-…",
  "event": "completed",
  "id": "pay_ab12cd",
  "ext_id": "order_8841",
  "amount": "10000",
  "matched_amount": "10000",
  "from_status": "awaiting_payment",
  "status": "completed",
  "updated_at": "2026-09-17T12:14:00Z",
  "method": "sbp",
  "phone_number": "+7919…",
  "bank": "Сбербанк",
  "holder": "Иван И."
}

amount и matched_amount — целые рубли, значения строками. event дублирует status. webhook_id совпадает с X-Idempotency-Key.

ЗаголовокОписание
X-Api-KeyВаш vp_live_…
X-TimestampUnix-секунды. Проверяйте \now − ts\< 300
X-SignatureHMAC-SHA256(secret, timestamp + "." + body), hex
X-Idempotency-KeyДедупликация повторов

Подпись считается от строки timestamp + "." + тело. Секрет — vps_… из кабинета.

Node.js

import { createHmac, timingSafeEqual } from "node:crypto";

function verify(rawBody, headers, secret) {
  const ts = headers["x-timestamp"];
  const sig = headers["x-signature"];
  if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false;
  const expected = createHmac("sha256", secret).update(`${ts}.${rawBody}`).digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(sig));
}

Python

import hashlib, hmac, time

def verify(raw: bytes, headers: dict, secret: str) -> bool:
    ts = headers.get("X-Timestamp", "")
    sig = headers.get("X-Signature", "")
    if abs(time.time() - int(ts)) > 300:
        return False
    expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, sig)

PHP

function verifyWebhook(string $raw, array $headers, string $secret): bool {
  $ts = $headers['X-Timestamp'] ?? '';
  $sig = $headers['X-Signature'] ?? '';
  if (abs(time() - (int)$ts) > 300) return false;
  $expected = hash_hmac('sha256', $ts . '.' . $raw, $secret);
  return hash_equals($expected, strtolower($sig));
}

Если ваш сервер вернул не 2xx, доставка повторяется из кабинета кнопкой «Повторить». Отвечайте 2xx и опирайтесь на X-Idempotency-Key.

В кабинете: URL на каждый сайт, кнопка «Проверить», журнал доставок.