GramPayBot пушит события сам — опрашивать API не нужно. Вы указываете один HTTPS-URL на проект, и на него приходят POST-запросы с JSON-телом при смене статуса счёта и событиях биллинга.

Настройка URL

В боте: проект → 🪝 ВебхукиНастроить URL. Там же доступны Отправить тест, Сменить секрет, Логи доставки и включение/отключение endpoint. Секрет для проверки подписи выдаётся на проект — храните его рядом с API-токеном.

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

Каждая доставка содержит:

ЗаголовокЗначение
Content-Typeapplication/json
GramPay-EventИмя события, например invoice_paid
GramPay-Delivery-IDУникальный ID доставки — удобен для дедупликации и поиска в логах
GramPay-TimestampВремя отправки
GramPay-Signaturesha256=<hex> — HMAC-SHA256 от сырого тела запроса
POST /webhook/<app_id>200 OK
  • GramPay-Eventinvoice_paid
  • GramPay-Signaturesha256=8f21…d491
{
  "update_type": "invoice_paid",
  "payload": {
    "public_id": "GU91XHr8AUNQ8l9r",
    "status": "paid",
    "amount_usd": "180.00",
    "paid_token": "usdt",
    "tx_hash": "8f21…d491"
  }
}
✓ invoice_paid

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

Подпись считается от байтов тела запроса как получены, ключ — webhook-секрет проекта:

const crypto = require('crypto');

function verify(rawBody, header, secret) {
  const expected = 'sha256=' + crypto.createHmac('sha256', secret).update(rawBody).digest('hex');
  return crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));
}

Два правила, которые чаще всего нарушают:

  1. Считайте HMAC от сырого тела до JSON-парсинга. Пересериализация (JSON.stringify(JSON.parse(body))) меняет байты и ломает подпись.
  2. Сравнивайте подписи константной по времени функцией (timingSafeEqual, hmac.compare_digest и аналоги).

Запросы с неверной подписью отвергайте с не-2xx статусом и не обрабатывайте.

Security-тест при сохранении URL

Когда вы сохраняете или тестируете URL, GramPayBot отправляет один и тот же тестовый payload три раза: без подписи, с неверной подписью и с корректной. Требования к валидному запросу:

  • ответ 2xx в течение 5 секунд, без повторов на этом шаге;
  • ответ по точному URL — редиректы (3xx) не выполняются и считаются ошибкой;
  • endpoint должен быть на вашей стороне, не URL GramPayBot.

Если ваш обработчик отвечает 2xx и на запросы с неверной подписью, настройка продолжится, но появится предупреждение безопасности: проверка подписи не применяется.

Тестовое тело выглядит как реальное событие, но с "update_type": "webhook_test":

{
  "update_id": 1,
  "update_type": "webhook_test",
  "request_date": "2026-07-17T09:00:00Z",
  "payload": { "app_id": "6d86f4e0-6ad2-4fb9-9f0c-f2eb9ab5c4c7", "app_name": "My Shop", "message": "Webhook test" }
}

Если обработчик принимает только invoice-события, разрешите и webhook_test — иначе тест (и кнопка «Отправить тест») будет падать, хотя реальные события доставлялись бы нормально.

Успех, повторы и отключение

Доставка считается успешной только при HTTP 2xx. При ошибке GramPayBot повторяет отправку — до 17 попыток в течение ~4 дней с нарастающими интервалами. Если все попытки исчерпаны, endpoint отключается, и вы получите уведомление; после починки его нужно включить заново.

Начальный security-тест не повторяется: расписание повторов не лечит просто неверный URL.

Требования к обработчику

  • Отвечайте быстро: проверили подпись, сохранили событие, вернули 2xx. Долгую бизнес-логику выполняйте асинхронно.
  • Будьте идемпотентны: доставка может повториться. Дедуплицируйте по GramPay-Delivery-ID или по паре update_type + payload.public_id.
  • Не доверяйте только событию для критичных операций: при желании сверьте статус через getInvoice.

Полный список событий и состав данных в каждом — в разделе «События и структура payload».

Следующий шаг

Первые 50 подтверждённых оплат — за наш счёт.

Получить $5 на старт ↗