Webhook
Webhook
Webhook (буквально «веб-крючок») — это договорённость между двумя сервисами: когда у первого происходит интересное событие, он сам делает HTTP-запрос на заранее зарегистрированный адрес второго и приносит туда данные о событии. Это инверсия привычной модели «клиент спрашивает — сервер отвечает»: тут сервер сам зовёт клиента, как только есть что сказать.
История
Термин «webhook» придумал и популяризовал американский разработчик Джефф Линдсей (Jeff Lindsay) в мае 2007 года — в посте «Web hooks to revolutionize the web» в своём блоге progrium.com. До этого похожие идеи носили другие названия: «HTTP callback», «PubSub», «push notification», но именно у Линдсея получилось ёмкое слово, которое прижилось.
Какую проблему решали в 2007 году. Веб тогда уже был большим, но интеграции между сервисами строились через polling — клиент дёргает API раз в N минут «есть новости?». Это работало, но было расточительно: 99% запросов возвращали «нет, ничего нового». Если ты хотел узнать о новой записи в чужом блоге, тебе либо приходилось опрашивать его RSS-ленту раз в полчаса, либо ставить тяжёлые инфраструктурные штуки вроде XMPP или Jabber-серверов. Линдсей предложил очень простую идею: «давайте просто разрешим сервисам слать друг другу HTTP POST, когда есть событие. Никаких новых протоколов. Просто URL и тело запроса».
Вехи развития:
- 2007 — Линдсей вводит термин, делает доклад на конференции
OSCON. - 2008 — GitHub одним из первых выкатывает webhooks (тогда —
post-receive hooks) для оповещения CI-серверов о push в репозиторий. Это во многом дало термину массовое распространение в разработке. - 2011 — Stripe запускается с webhooks в качестве основного канала уведомлений о платежах. Платёж прошёл — Stripe пинает твой endpoint. Это стало эталоном «как делать webhooks правильно»: подпись HMAC, ретраи, версионирование.
- 2012 — Twilio делает webhooks основным механизмом обработки входящих SMS и звонков: пришло сообщение → Twilio дёргает твой URL → твой код возвращает XML-инструкцию что сказать абоненту.
- 2013 — выходит спецификация WebSub (бывший PubSubHubbub) от Google — попытка стандартизировать webhook-механизм для подписки на ленты контента. Стала рекомендацией W3C в 2018.
- 2018 — Telegram Bot API получает поддержку webhooks как альтернативу long polling: бот регистрирует HTTPS-URL, Telegram сам шлёт туда каждое сообщение.
- 2020-е — webhooks становятся фактическим стандартом для всех SaaS-интеграций: Shopify, Slack, Notion, Linear, HubSpot, Bitrix24, amoCRM — у каждого свой каталог событий и формат payload.
Текущий статус: webhook — это не один протокол, а паттерн поверх HTTP. Никто не владеет «торговой маркой», нет одного стандарта (хотя есть попытки — Standard Webhooks от 2022 года, инициатива Svix). Каждый сервис делает по-своему: где-то JSON, где-то form-encoded, где-то XML; подпись где-то HMAC-SHA256, где-то JWT, где-то вообще нет.
Что это такое
Webhook — это обратный HTTP-запрос от сервера к клиенту в ответ на событие. Слово «обратный» здесь ключевое: в классическом веб-взаимодействии клиент (твой браузер, твой код) первым стучится к серверу, и сервер отвечает. В webhook-модели роли меняются — это сервис, у которого случилось событие, сам становится клиентом и стучится к твоему серверу.
Технически webhook — это всегда HTTP-запрос (чаще всего POST, иногда PUT) на URL, который ты заранее указал у источника событий. В теле запроса — данные о событии, обычно в формате JSON. Твой сервер должен:
- Слушать этот URL и принимать запросы.
- Прочитать payload, понять что произошло.
- Сделать что-то полезное (записать в БД, послать сообщение, обновить статус).
- Вернуть HTTP-код 200 (или 2xx), чтобы источник понял: «принято».
Если ты вернул 5xx или вообще не ответил — источник, скорее всего, попробует прислать запрос ещё раз через минуту, через 10 минут, через час. Это называется retry policy (политика повторов).
Webhook vs Polling. Polling — это когда клиент сам периодически спрашивает «есть новости?». Webhook — когда сервер сам говорит «новости есть, лови». Разница как между «звонишь сам в магазин каждый час: привезли ли заказанный диван» и «магазин звонит тебе, когда диван приехал». Polling простой, но тратит ресурсы и даёт задержку (узнаешь только при следующем опросе). Webhook эффективнее по ресурсам и почти мгновенный, но требует, чтобы у тебя был публичный URL.
Webhook vs API. Это не альтернативы, а дополнения. API — это про «я хочу что-то сделать или узнать прямо сейчас». Webhook — про «дай знать, когда что-то случится». В Stripe ты дёргаешь API, чтобы списать деньги, и слушаешь webhook, чтобы узнать что списание реально прошло (потому что 3D-Secure, банк-эмитент и прочие задержки могут добавить минуты).
Webhook vs WebSocket. WebSocket — это постоянное двустороннее соединение, которое держится открытым часами. Webhook — это серия одноразовых HTTP-запросов. WebSocket хорош для чата и онлайн-игр, где сообщения летают каждые миллисекунды. Webhook — для редких бизнес-событий: «платёж прошёл», «лид пришёл», «коммит запушен».
Webhook vs Callback (в коде). В программировании «callback» — это функция, которую ты передаёшь куда-то, чтобы её вызвали потом. Webhook — буквально та же идея, только не функция, а URL, и вызов через сеть, а не в памяти процесса. Иногда webhook так и называют — «HTTP callback».
Аналогии из жизни
Аналогия 1: курьер с домофоном. Polling — это когда ты каждые пять минут спускаешься во двор «ну где же мой курьер». Webhook — когда ты дал курьеру свой код домофона, курьер сам нажимает на кнопку, когда подошёл, и ты идёшь открывать.
Где ломается: если домофона нет (нет публичного URL, ты за NAT-роутером без проброса портов), курьер не сможет тебя позвать — придётся всё-таки выходить и проверять. И ещё: если ты не услышал звонок (сервер лежал), курьер постоит, позвонит ещё раз, а потом уйдёт. Поэтому у webhook всегда есть retry policy, а у тебя должна быть готовность принять «звонок» повторно, не запутавшись (см. идемпотентность).
Аналогия 2: подписка на доставку газеты. Раньше ты приходил в киоск каждое утро и смотрел, вышел ли свежий номер (polling). Подписка работает наоборот: газета сама приходит в твой почтовый ящик в день выхода (webhook). Адрес доставки ты сообщил один раз при оформлении подписки — это и есть «регистрация webhook URL».
Где ломается: почтальон не знает, прочитал ли ты газету. Если ящик переполнен или ты переехал, газета будет лежать в подъезде или вернётся отправителю. С webhook так же: если твой сервер ответил 200, источник считает «доставлено» и забывает — даже если у тебя внутри код упал и реально событие не обработалось. Поэтому грамотная схема — сначала записать payload «как есть» в свою очередь, ответить 200, и только потом разбирать.
Аналогия 3: радио-маяк аэропорта. Маяк не знает, кто его слушает. Он просто кричит «вот моя позиция» в эфир, а все приёмники, кто на него настроен, ловят сигнал. Только это аналогия для pub/sub (издатель/подписчик), а не строго для webhook: webhook обычно адресный — конкретный URL.
Где ломается: маяк широковещателен — webhook индивидуален. Если у источника 10 000 подписчиков и нужно разослать им всем уведомление, источнику придётся сделать 10 000 отдельных HTTP-запросов. Это нагрузка, и поэтому крупные сервисы (Slack, GitHub) ограничивают количество подписчиков на одно событие или предлагают использовать pub/sub поверх специальной шины сообщений (Kafka, EventBridge), а не webhook.
Как это работает
Жизненный цикл webhook условно делится на три этапа: регистрация → доставка → обработка.
Этап 1. Регистрация.
Ты идёшь в админку источника событий (или через его API) и говоришь: «Когда у тебя случается событие типа payment.succeeded, шли POST на https://my-server.com/hooks/stripe». Источник запоминает: «такой-то аккаунт хочет такие-то события на такой URL». Часто при регистрации генерируется секрет (shared secret) — длинная случайная строка, которой обе стороны будут пользоваться для подписи.
Этап 2. Доставка.
У источника случается событие. Он формирует payload (объект с описанием события — обычно JSON), вычисляет подпись HMAC-SHA256(secret, body), открывает HTTP-соединение к твоему URL и делает запрос:
POST /hooks/stripe HTTP/1.1
Host: my-server.com
Content-Type: application/json
Stripe-Signature: t=1738012345,v1=5257a869e7ecebeda32...
User-Agent: Stripe/1.0 (+https://stripe.com/docs/webhooks)
{
"id": "evt_1Mvz8...",
"type": "payment_intent.succeeded",
"created": 1738012345,
"data": { "object": { "amount": 100000, "currency": "rub", ... } }
}
Дальше источник ждёт ответ — обычно с таймаутом 5–30 секунд. Что бы ни случилось — твой сервер должен уложиться. Если ответил 2xx — событие считается доставленным. Если 4xx — источник, как правило, не повторяет (значит, ты сам отказался). Если 5xx или таймаут — включается retry policy: повторы с экспоненциальным интервалом (1 минута → 5 минут → 30 минут → час → 6 часов → день), обычно в течение 3–7 суток. После исчерпания попыток событие отправляется в «архив недоставленных» или просто теряется, в зависимости от сервиса.
Этап 3. Обработка.
Твой сервер получает запрос. Хорошая схема выглядит так:
- Проверить подпись. Берёшь заголовок
Stripe-Signature(илиX-Hub-Signature, или как назвал источник), вычисляешь HMAC от тела запроса с тем же секретом, сравниваешь. Не совпало — отвечаешь 401 и логируешь. Это защита от подделок: без секрета злоумышленник не сможет имитировать webhook от Stripe. - Записать payload в очередь. Не пытайся обработать прямо в HTTP-обработчике — слишком долго, источник отключится по таймауту. Сохрани событие в свою БД или очередь и сразу верни 200.
- Проверить идемпотентность. Источник может прислать одно и то же событие дважды (например, ты ответил 200, но ответ не дошёл, и через минуту он повторил). У каждого события обычно есть уникальный ID (
evt_1Mvz8...у Stripe). Если ты уже видел этот ID — игнорируй. - Сделать дело. Фоновый воркер берёт событие из очереди, обновляет статус заказа, шлёт письмо клиенту, пишет в CRM. Если что-то падает — воркер ретраит сам, источник об этом уже не знает.
ASCII-схема всего цикла:
┌──────────────┐ ┌──────────────┐
│ Источник │ 1. регистрация URL+secret │ Получатель │
│ (Stripe) │<─────────────────────────────│ (my-server) │
└──────┬───────┘ └──────┬───────┘
│ │
│ ───── событие случилось ───── │
│ │
│ 2. POST /hooks/stripe + HMAC │
│ ────────────────────────────────────────────>│
│ │ 3. проверить
│ │ подпись,
│ │ в очередь
│ 4. HTTP 200 OK │
│ <────────────────────────────────────────────│
│ │
│ (если 5xx или таймаут — retry через 1m, │
│ 5m, 30m, 1h, 6h, 24h …) │
▼ ▼
Ключевое
Webhook — это не «технология», а договорённость двух сторон поверх HTTP. Все «фишки» webhook (подписи, ретраи, идемпотентность, версионирование) — это надстройки, которые каждый сервис делает по-своему. Если ты выкатил свой webhook, ты сам отвечаешь за качество этой договорённости.
Где встречается в обычной жизни
Ты сталкиваешься с webhook практически каждый день, не зная этого слова:
- Уведомление о платеже на телефон. Купил кофе в Starbucks по карте — банк прислал push-уведомление. Между терминалом, банком-эквайером и приложением банка пробежал целый караван webhook'ов: терминал дёрнул банк-эквайер, тот сходил в банк-эмитент, банк-эмитент отбил webhook своему приложению, приложение показало пуш.
- Telegram-бот ответил на твоё сообщение. Если бот работает не на long polling, а на webhook — каждое твоё сообщение Telegram доставляет на сервер бота HTTP POST'ом. Бот обработал, ответил, ты увидел.
- GitHub-страница пересобралась через минуту после твоего push. Это webhook от GitHub дёрнул CI-сервер, тот собрал и задеплоил.
- Письмо «ваш заказ собран» от Ozon/Wildberries. Внутри склада софт менял статус заказа, статус через webhook улетел в почтовый сервис (SendGrid, Mailgun), тот сформировал и отправил письмо.
- «У вас новый отзыв» в Booking.com. Гость оставил отзыв — Booking стрельнул webhook в твой бэкенд, твой бэкенд отметил его и прислал тебе пуш.
Где встречается в IT и бизнесе
- CRM-интеграции. Лид заполнил форму на лендинге — лендинг шлёт webhook в amoCRM/Bitrix24, там автоматически создаётся карточка сделки. Менеджер видит лид через секунды, а не через час по выгрузке.
- Биллинг и платежи. Stripe, PayPal, ЮKassa, CloudPayments — все используют webhook для асинхронных событий: платёж прошёл, возврат сделан, подписка отменена. Без webhook ты бы дёргал API «есть ли новые платежи?» каждые 30 секунд и всё равно опаздывал.
- CI/CD. GitHub/GitLab/Bitbucket шлют webhook в Jenkins, CircleCI, GitHub Actions при push, merge, тег-релизе. Это запускает сборку, тесты, деплой.
- Чат-боты и колл-центры. Telegram, WhatsApp Business API, MAX API, Viber — все принимают сообщения через webhook на твой бот-сервер. То же — для интеграций с call-tracking сервисами и колл-центрами: новый звонок → webhook → запись в CRM с привязкой к рекламной кампании.
- Аналитика и трекинг событий. Mixpanel, Amplitude, Segment, ClickHouse Cloud — все умеют принимать события через webhook-endpoint, чтобы агрегировать их в дашборды.
- Мониторинг и алерты. Sentry, Datadog, PagerDuty шлют webhook в Slack-канал команды, когда что-то упало.
Кто пользуется
Конкретные крупные пользователи webhook'ов — это, считай, все продукты, которые делают B2B SaaS:
- Stripe обрабатывает миллионы webhook'ов в сутки только для своих клиентов. По их публичной документации, у них около 200 типов событий — от
payment_intent.succeededдоcustomer.subscription.trial_will_end. - GitHub ежедневно рассылает миллиарды webhook-событий по миллионам репозиториев — каждый push, PR, issue, comment может триггерить webhook.
- Slack через webhook (Incoming Webhooks) принимает сообщения от внешних сервисов в каналы — это один из самых популярных способов интеграции мониторинга и CI в команды.
- Telegram на пике принимает сотни тысяч событий в секунду через Bot API и доставляет их через webhook в боты по всему миру (точных цифр не знаю — публичной статистики Telegram почти не публикует).
- Shopify через webhook оповещает магазины о новых заказах, изменении инвентаря, регистрации клиентов; для крупных магазинов это десятки тысяч событий в день.
- Twilio обрабатывает входящие SMS и звонки исключительно через webhook — это его архитектурная основа.
- В России: ЮKassa, CloudPayments, Тинькофф Касса, amoCRM, Bitrix24, Яндекс.Метрика (через API офлайн-конверсий) — все строят интеграции на webhook'ах.
Альтернативы и конкуренты
- Polling. Плюсы: проще всего реализовать; работает без публичного URL; ты контролируешь нагрузку. Минусы: задержка между событием и реакцией; впустую тратит трафик и квоты API; плохо масштабируется при росте количества клиентов.
- Long polling. Плюсы: «как polling, но почти мгновенно»; не нужен публичный URL получателя. Минусы: нужно держать открытое соединение долго; нагружает сервер источника постоянными «висячими» запросами. Так работает Telegram Bot API по умолчанию.
- Server-Sent Events (SSE). Плюсы: стандарт HTML5, работает поверх HTTP; одно соединение, на которое сервер кидает события; браузер сам реконнектит. Минусы: только в одну сторону (сервер → клиент); не очень подходит для server-to-server.
- WebSocket. Плюсы: двустороннее соединение; мгновенный обмен; нет накладных расходов на каждое сообщение. Минусы: сложнее в реализации и эксплуатации (постоянное соединение надо держать живым, реконнектить, балансировать); не подходит для редких событий.
- Message queues (Kafka, RabbitMQ, AWS SNS/SQS, Google Pub/Sub). Плюсы: гарантии доставки, порядок, миллионы сообщений в секунду, развязка отправителя и получателя. Минусы: тяжёлая инфраструктура; не подходит для интеграций «через интернет с третьей стороной», в основном для внутренних систем компании.
- gRPC streaming. Плюсы: типизированные контракты, высокая производительность, двусторонние потоки. Минусы: требует gRPC на обеих сторонах, плохо проходит через корпоративные файрволы, редко используется для публичных API.
Когда НЕ стоит использовать
- Когда у получателя нет публичного HTTPS-URL. Webhook — это входящий HTTP-запрос. Если ты сидишь за NAT, на ноутбуке без проброса портов, на машине, которую периодически выключают, — источник не дозвонится. Решения: либо поднять сервер на VPS, либо использовать туннели типа
ngrok/cloudflared(хорошо для разработки, плохо для прода), либо переключиться на polling. - Когда критичен строгий порядок событий. Webhook'и могут прийти не в том порядке, в каком случились (особенно при ретраях). Если для тебя важно «сначала пришло событие А, потом Б», тебе нужны либо timestamp'ы внутри payload (тогда ты сам пересортируешь), либо вообще не webhook, а message queue с гарантией порядка.
- Когда получатель часто и подолгу недоступен. У большинства источников retry policy ограничена сутками. Если твой сервис лежит неделю — события просто потеряются. Тут нужна надёжная очередь между источником и тобой.
- Когда события идут с очень высокой частотой. Если ты хочешь принимать миллион событий в секунду — webhook не лучший выбор: каждый отдельный HTTP-запрос имеет накладные расходы (TLS-handshake, заголовки, парсинг). Тут выигрывает batch-API или потоковая очередь.
- Когда тебе нужна гарантия «ровно один раз» (exactly-once delivery). Webhook гарантирует «хотя бы один раз» — у тебя обязательно будут дубликаты. Если бизнес-логика не толерантна к дубликатам без идемпотентности — придётся либо городить идемпотентность, либо менять архитектуру.
Главный грабли
Никогда не доверяй webhook без проверки подписи. Любой, кто узнал твой публичный URL (а это легко — он в DNS, его видят CDN, прокси, логи), может слать на него поддельные запросы. Если ты на основе webhook'а от «Stripe» (на самом деле — от хакера) активируешь подписку — поздравляю, ты раздаёшь премиум-аккаунты бесплатно. Проверка HMAC-подписи — обязательна.
Связанные понятия
- HMAC (Hash-based Message Authentication Code) — способ подписать сообщение секретным ключом, чтобы получатель убедился: данные не подменили и автор знает секрет.
- Idempotency key — уникальный идентификатор операции, который позволяет безопасно повторять её без побочных эффектов. Ключевой инструмент для надёжных webhook-обработчиков.
- Retry с экспоненциальным бэкоффом — стратегия повторов, при которой интервал между попытками растёт (1 сек, 2, 4, 8, 16…). Защищает источник от лавины запросов к лежащему получателю.
- Dead Letter Queue (DLQ) — «почта недоставленных» для событий, которые не получилось обработать за все попытки. Туда складывают, чтобы потом разобрать руками.
- Long polling — компромисс между обычным polling и webhook: клиент делает запрос с долгим таймаутом, сервер держит соединение, пока не появится событие.
- Pub/Sub (publish/subscribe) — архитектурный паттерн «один отправитель, много подписчиков». Webhook — это его частный случай поверх HTTP.
- WebSub — стандарт W3C на webhook-механизм подписки на ленты (RSS, Atom). Расширение оригинального PubSubHubbub.
Литература и источники
- Wikipedia ru «Веб-хук» —
ru.wikipedia.org/wiki/Веб-хук. Базовое определение и история. - Wikipedia en «Webhook» —
en.wikipedia.org/wiki/Webhook. Более полная статья с примерами и сравнениями. - Stripe Webhooks documentation —
stripe.com/docs/webhooks. Эталонная документация: как подписывать, как проверять подпись, как ретраить, как версионировать события. Полезно прочитать даже если ты Stripe не используешь — это «как надо делать». - GitHub Webhooks documentation —
docs.github.com/en/webhooks. Тоже отличный референс, особенно интересна часть про события и payload. - «Standard Webhooks» initiative —
standardwebhooks.com. Молодая попытка унификации (с 2022). Авторство — компания Svix; среди соавторов спецификации — Twilio, Lob, Mux. Status: предложение, не повсеместный стандарт. - Jeff Lindsay, «Web hooks to revolutionize the web» (2007) — оригинальный пост, с которого всё началось. Искать в Google «Jeff Lindsay web hooks 2007», есть копии в Wayback Machine.
- Книга «Designing Data-Intensive Applications» (Martin Kleppmann, 2017, en) — глава про обмен сообщениями (Messaging Systems) даёт контекст, где webhook стоит в общей картине inter-service communication.
Где встретилось у меня
Понятие всплыло вчера в нескольких задачах. В одном из бот-проектов (для платформы MAX) приём входящих сообщений построен ровно по webhook-схеме: внешняя платформа стучится к серверу бота, бот разбирает payload и пишет в БД. В соседнем проекте (управление рекламными ссылками) у каждого проекта в БД есть поле для URL вебхука колл-центра — туда отправляется уведомление о новой заявке, чтобы оператор не дёргал систему руками. Также вчера разбирался поток данных «лидогенератор → бренд-бот»: и там, и там — webhook'и c подписью и очередью на принимающей стороне.
Краткое резюме
- Webhook = HTTP POST от источника к получателю в момент события. Инверсия привычной схемы «клиент дёргает сервер».
- Главные плюсы: мгновенно, эффективно по ресурсам, просто (всё поверх обычного HTTP).
- Главные ограничения: нужен публичный URL у получателя, нет гарантии порядка, доставка «хотя бы один раз» — значит, нужна идемпотентность.
- Минимум для прода: HTTPS, проверка HMAC-подписи, запись в очередь до возврата 200, dedup по event-id, нормальные логи.
- Альтернативы выбираешь по задаче: polling — для простых случаев и отсутствия публичного URL, message queue — для надёжных внутренних потоков, WebSocket/SSE — для постоянно живых соединений.