GramPayBot пушит события сам — опрашивать API не нужно. Вы указываете один HTTPS-URL на проект, и на него приходят POST-запросы с JSON-телом при смене статуса счёта и событиях биллинга.
Настройка URL
В боте: проект → 🪝 Вебхуки → Настроить URL. Там же доступны Отправить тест, Сменить секрет, Логи доставки и включение/отключение endpoint. Секрет для проверки подписи выдаётся на проект — храните его рядом с API-токеном.
Заголовки запроса
Каждая доставка содержит:
| Заголовок | Значение |
|---|---|
Content-Type | application/json |
GramPay-Event | Имя события, например invoice_paid |
GramPay-Delivery-ID | Уникальный ID доставки — удобен для дедупликации и поиска в логах |
GramPay-Timestamp | Время отправки |
GramPay-Signature | sha256=<hex> — HMAC-SHA256 от сырого тела запроса |
- 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));
}
Два правила, которые чаще всего нарушают:
- Считайте HMAC от сырого тела до JSON-парсинга. Пересериализация (
JSON.stringify(JSON.parse(body))) меняет байты и ломает подпись. - Сравнивайте подписи константной по времени функцией (
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».