Skip to main content
Вебхук — POST на адрес вашего сервера, когда с вашим клиентом что-то произошло. Он избавляет от опроса: оплату вы видите сразу, а клиенту, у которого кончается срок, можете напомнить о продлении сами. Напоминания платформы получают только клиенты с Telegram, клиентам вашего бота или сайта напоминаете вы. События приходят по клиентам и счетам, заведённым через API. Клиенты ваших Telegram-ботов на платформе событий не порождают. Пока доступ к API выключен, событий нет вовсе — и потом они не досылаются. Источник истины по-прежнему запрос GET. Вебхук может прийти дважды, опоздать или прийти не по порядку. Перед действием по событию перечитайте счёт или клиента.

Подключение

1

Адрес

В кабинете партнёра откройте раздел «API» и добавьте адрес в блоке «Адреса вебхуков». Подходит только https:// на порту 443 с доменным именем, которое указывает на публичный адрес сервера; IP-адрес вместо имени не принимается. Адресов может быть до 10, для каждого выбираются свои события.
2

Секрет

Скопируйте секрет адреса (whsec_…) и сохраните его на сервере. Им проверяется подпись, а посмотреть его снова можно пунктом «Секрет подписи» в меню адреса (⋯ в его строке).
3

Тест

В меню адреса выберите «Отправить тест». Придёт событие webhook.test с пустым data, а кабинет покажет, дошло ли оно: код ответа вашего сервера, при успехе — и время ответа. Тест — не чаще 5 раз в минуту на адрес.
Событие приходит только на адреса, которые в момент события включены и подписаны на его тип. Адрес, добавленный или включённый позже, прошедших событий не получает.

Запрос

  • webhook-id — номер события. Он одинаков во всех повторах одного события: по нему отсекайте дубли.
  • webhook-timestamp — время отправки этой попытки, Unix-секунды.
  • webhook-signature — подпись. Во время перевыпуска секрета подписей две через пробел: новым секретом и прежним.
  • Тело: type — тип события, timestamp — когда событие произошло (ISO 8601, UTC, с долями секунды), data — объект события. Тело одинаково во всех повторах.

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

Каждое событие подписано секретом адреса. По подписи ваш сервер понимает, что запрос пришёл от VARGO и по дороге не изменился: адрес публичный, отправить на него запрос может кто угодно. Подпись сделана по открытому стандарту Standard Webhooks, поэтому проверить её можно готовой библиотекой, без своего кода.
  • Тело — как пришло. Подпись считается от тела байт в байт, поэтому передавайте в проверку сырое тело до разбора JSON: await request.body() в FastAPI, express.raw в Express. Разобранный и собранный заново JSON проверку не пройдёт.
  • Часы сервера — точные. Запрос, отправленный больше 5 минут назад, библиотека отклоняет: так не пройдёт повтор перехваченного запроса. Если часы сервера отстают, отклоняться будут и настоящие события.
Библиотеки стандарта есть и для Go, PHP, Ruby, Java, C#, Rust — список на standardwebhooks.com.
  1. Соберите строку {webhook-id}.{webhook-timestamp}.{тело}, тело — как пришло.
  2. Ключ — секрет без префикса whsec_, декодированный из base64.
  3. Посчитайте HMAC-SHA256 строки на этом ключе и закодируйте результат в base64.
  4. В webhook-signature через пробел лежат подписи вида v1,<base64>. Событие подлинное, если ваша подпись совпала хотя бы с одной из них. Сравнивайте функцией постоянного времени: hmac.compare_digest в Python, crypto.timingSafeEqual в Node.js.
  5. Отклоните запрос, если webhook-timestamp отличается от текущего времени больше чем на 5 минут.

Ответ и повторы

Событие считается доставленным, когда ваш сервер ответил кодом 2xx не дольше чем за 15 секунд. Тело ответа не важно. Отвечайте сразу, а долгую работу делайте после ответа. Любой другой исход — код не 2xx, таймаут, обрыв соединения, редирект — это сбой, и событие приходит снова. Всего 10 попыток. Повторы идут через 5 секунд, 5 минут, 30 минут, 2, 5, 10, 14, 20 и 24 часа, с разбросом ±10%, так что последняя попытка приходит примерно через трое суток. Если в ответе с ошибкой есть заголовок Retry-After, следующая попытка придёт не раньше этой паузы; пауза длиннее 24 часов считается за 24 часа. Раньше срока по расписанию попытка не придёт. Адрес отключается сам, если он пять суток подряд не принял ни одного события или однажды ответил на событие 410 Gone (ответ на тест адрес не отключает). Тогда вам придёт сообщение в бот @vargo_robot. Включить адрес снова можно в кабинете. После починки сервера события, которые до него не дошли, отправляются заново пунктом «Повторить неудачные» в меню адреса: за последние сутки, 3, 7 или 30 дней. Повтор возвращает только доставки, которые не прошли. Чего он не вернёт:
  • события, случившиеся, пока адрес был выключен: на выключенный адрес события не ставятся вовсе;
  • события, ждавшие отправки, когда вы выключили адрес сами: они отменяются.
Чтобы не потерять события на время работ на сервере, адрес не выключайте: недошедшие доставки повторяются по расписанию, а остаток вернёт «Повторить неудачные». Состояние клиента после перерыва сверьте запросом GET /clients.

Секрет

Секрет адреса можно перевыпустить в кабинете. Следующие 24 часа события подписываются двумя секретами, новым и прежним, поэтому сервер, который ещё не получил новый секрет, продолжает их принимать. Замените секрет на сервере в течение этих суток.

События

Новые типы событий появляются без смены версии. Адрес, подписанный на все события, получит и их: незнакомый тип отвечайте 2xx и пропускайте.

payment.succeeded

Счёт оплачен, доступ клиенту выдан. data — счёт в том же виде, что в ответе GET /payments/{payment_id}: status: succeeded и клиент со ссылкой подписки в client. timestamp события равен paid_at счёта.

payment.expired

Счёт не оплачен: клиент не заплатил за 30 минут или оплата не прошла — банк отказал, клиент закрыл страницу оплаты. Событие приходит, когда сервис оплаты отменит счёт, — бывает и раньше expires_at, — а если отмены нет, то в течение часа после expires_at. timestamp — момент, когда счёт помечен истёкшим. data — счёт со status: expired и client: null. Истечение не окончательно: если клиент всё же заплатит позже, оплата пройдёт, и следом придёт payment.succeeded. По этому событию удобно напомнить клиенту о брошенной оплате.

subscription.expiring

До конца срока подписки 72 часа или 24 часа. Каждый порог приходит один раз за срок. Клиент, у которого до конца меньше 24 часов в момент первой проверки, получит только порог 24. У пробной подписки порога 72 нет: столько длится весь пробный период. После продления отсчёт начинается заново.
client — в том же виде, что ответ GET /clients/{client_id}.

subscription.expired

Срок подписки вышел. data — {"client": …}, timestamp — момент окончания срока. Событие ставится в течение суток после окончания срока; подписка, истёкшая раньше, его уже не получит. Если клиент продлит подписку, после следующего окончания событие придёт снова. События срока проверяются раз в 5 минут. Заблокированным клиентам они не приходят.