POST на адрес вашего сервера, когда с вашим клиентом что-то произошло. Он избавляет от опроса: оплату вы видите сразу, а клиенту, у которого кончается срок, можете напомнить о продлении сами. Напоминания платформы получают только клиенты с Telegram, клиентам вашего бота или сайта напоминаете вы.
Источник истины по-прежнему запрос GET. Вебхук может прийти дважды, опоздать или прийти не по порядку. Перед действием по событию перечитайте счёт или клиента.
Подключение
1
Адрес
В кабинете партнёра откройте раздел «API» и добавьте адрес в блоке «Адреса вебхуков». Подходит только
https:// на порту 443 с публичным адресом сервера. Адресов может быть до 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 минут назад, библиотека отклоняет: так не пройдёт повтор перехваченного запроса. Если часы сервера отстают, отклоняться будут и настоящие события.
Проверка без библиотеки
Проверка без библиотеки
- Соберите строку
{webhook-id}.{webhook-timestamp}.{тело}, тело — как пришло. - Ключ — секрет без префикса
whsec_, декодированный из base64. - Посчитайте HMAC-SHA256 строки на этом ключе и закодируйте результат в base64.
- В
webhook-signatureчерез пробел лежат подписи видаv1,<base64>. Событие подлинное, если ваша подпись совпала хотя бы с одной из них. Сравнивайте функцией постоянного времени:hmac.compare_digestв Python,crypto.timingSafeEqualв Node.js. - Отклоните запрос, если
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 дней.
Секрет
Секрет адреса можно перевыпустить в кабинете. Следующие 24 часа события подписываются двумя секретами, новым и прежним, поэтому сервер, который ещё не получил новый секрет, продолжает их принимать. Замените секрет на сервере в течение этих суток.События
Новые типы событий появляются без смены версии. Адрес, подписанный на все события, получит и их: незнакомый тип отвечайте2xx и пропускайте.
payment.succeeded
Счёт оплачен, доступ клиенту выдан.data — счёт в том же виде, что в ответе GET /payments/{payment_id}: status: succeeded и клиент со ссылкой подписки в client.
payment.expired
Счёт истёк неоплаченным: клиент не заплатил за 30 минут. Событие приходит, когда сервис оплаты отменит счёт, и не позже чем через час после истечения.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 минут. Заблокированным клиентам они не приходят.
