Що таке вебхук: гачок, що будить інший сервіс

Вебхук (webhook) — це користувацький HTTP-зворотний виклик: коли в одній системі стається подія, вона сама надсилає запит на URL іншої системи. Англійською це user-defined HTTP callback. Джерело не чекає, поки його «опитають». Воно стукає в момент події і передає тіло запиту — зазвичай JSON-payload із типом події, часовою міткою та даними об’єкта.

Коротко: вебхук — це push-модель інтеграції. Клієнт не тягне дані циклом «чи вже є новина?», а підписується на подію і отримує POST у власний endpoint у ту саму секунду, коли щось змінилось.

Саме тому HTTP-колбеки стали клеєм між CRM, платіжними шлюзами, CI/CD, чатами й no-code платформами. Нижче — не словникова довідка, а розбір механіки, історії, меж застосування, безпеки, типових збоїв і того, як відрізнити «налаштував за вечір» від задачі для інженера інтеграцій.

Механізм доставки: хто кого будить і що летить мережею

Класичний REST API працює за схемою «спитав — відповів». Вебхук розвертає стрілку: джерело події стає ініціатором. GitHub після push у репозиторій, Stripe після успішної оплати, інтернет-магазин після нового замовлення — кожен із них формує повідомлення і відправляє його на заздалегідь збережену адресу підписника.

Ланцюжок виглядає так. Спочатку в системі-відправнику реєструють URL приймача й перелік подій. Далі, коли подія справді сталася, відправник серіалізує об’єкт (замовлення, платіж, коміт), додає заголовки з ідентифікатором доставки та криптографічним підписом, і виконує HTTPS POST. Приймач має швидко відповісти кодом 2xx — і лише після цього братися за важку роботу: записати в базу, надіслати лист, оновити склад.

Анатомія типового запиту

Метод майже завжди POST. GET трапляється в застарілих або спрощених схемах і погано підходить для тіла з даними. Заголовок Content-Type — application/json. Окремо йдуть службові заголовки: тип події, унікальний ID доставки, підпис HMAC, інколи часова мітка. Тіло містить подію, а не «сирий» бізнес-об’єкт у вакуумі: є поле type на кшталт payment_intent.succeeded або push, час і вкладений data.

Є два стилі корисного навантаження. «Товсте» (fat / snapshot) несе повний знімок об’єкта на момент події — зручно, але важче і швидше застаріває, якщо об’єкт зміниться через мілісекунди. «Тонке» (thin) несе ідентифікатор і тип: приймач сам дотягує актуальний стан через API. Stripe дає обидва підходи залежно від версії API. Standard Webhooks радить thin-модель саме через аудит і узгодженість.

Що відправник вважає успіхом

Успіх — будь-який 2xx, отриманий до таймауту. 3xx із редіректом багато провайдерів не люблять: підпис рахується на конкретний URL, а проксі по дорозі може зламати тіло. 410 Gone — сигнал «цю підписку можна вимкнути». 429 Too Many Requests просить зменшити темп; коректний відправник дивиться Retry-After. 5xx і обрив з’єднання запускають повторну доставку.

Критичне правило продакшену: спочатку зберегти подію в чергу й відповісти 200 OK, і лише потім обробляти. GitHub чекає відповідь близько 10 секунд. Shopify — приблизно 1 секунду на з’єднання і 5 секунд на весь запит. Хто рахує знижку, пише в ERP і шле SMS у тому ж HTTP-запиті, той гарантовано зловить timeout і дубль.

Повторна доставка — не збій протоколу, а його частина. Stripe в live-режимі ретраїть невдалі спроби з експоненційною паузою до трьох діб: майже одразу, потім через 5 і 30 хвилин, 2, 5 і 10 годин, далі приблизно кожні 12 годин. Пісочниця обмежена трьома спробами за кілька годин. Звідси вимога ідемпотентності: той самий event.id не повинен двічі нарахувати оплату чи створити друге замовлення.

Від «hook» Ліндсея до Standard Webhooks

Слово webhook з’явилося 3 травня 2007 року. Розробник Джеф Ліндсей (Jeff Lindsay) у тексті «Web hooks to revolutionize the web» запропонував простий прийом: користувач вказує URL, а застосунок робить POST, коли відбувається щось цікаве. Назву він склав із програмного hook — точки вбудовування стороннього коду — і префікса web, бо каналом став звичайний HTTP.

Ідея потрапила в родючий ґрунт. Наприкінці 2000-х API вже були звичними, але інтеграції залишалися опитувальними: кожні N секунд клієнт питав «чи є зміни?». Для рідкісних подій це марна витрата ресурсів. GitHub зробив HTTP-колбеки буденністю CI: push запускає збірку, не змушуючи Jenkins крутитись у циклі. Платіжні сервіси — PayPal, згодом Stripe — почали повідомляти магазин про charge.succeeded, dispute.created, customer.subscription.deleted. Slack incoming webhooks перетворили канал команди на приймач подій з усього стеку.

У 2020-х з’явилась втома від зоопарку заголовків. Stripe-Signature, X-Hub-Signature-256, X-Shopify-Hmac-SHA256 — усі роблять одне, але бібліотека для одного провайдера не підходить іншому. Специфікація Standard Webhooks (спільнота standard-webhooks, Apache 2.0) запропонувала спільні заголовки webhook-id, webhook-timestamp і webhook-signature, підпис рядка id.timestamp.payload і секрет формату whsec_. Паралельно CNCF просуває CloudEvents — спільну обгортку для подій. У 2024-му IETF зафіксував RFC 9421 (HTTP Message Signatures): канонічний підпис методу, шляху, заголовків і тіла. У 2025–2026 роках частина платформ почала віддавати Signature-Input поруч зі старими HMAC-заголовками. Міграція повільна, але напрямок уже не дискусійний.

Де вебхук виграє, а де програє

Подієве сповіщення — не універсальний транспорт. Його сила в рідкісних, важливих, односпрямованих сигналах. Слабкість — у відсутності постійного каналу, гарантованого порядку і зручного двостороннього діалогу. Нижче — робоча матриця вибору, а не реклама «завжди беріть вебхуки».

Критерій Вебхук Polling (опитування) WebSocket REST API за запитом
Хто починає Джерело події Клієнт за розкладом Будь-яка сторона після handshake Клієнт, коли треба дані
З’єднання Коротке HTTP на кожну подію Коротке HTTP, багато разів Довге TCP, тримається відкритим Коротке HTTP на виклик
Затримка Секунди, інколи менше Довжина інтервалу опитування Мілісекунди Час запиту-відповіді
Навантаження в тиші Майже нуль Постійне, навіть без змін Keep-alive, пінг Нуль, доки ніхто не кличе
Порядок і дублі Порядок не гарантований; дублі нормальні Клієнт сам зводить стан Потік, але треба heartbeat Ідемпотентність через ключі запиту
Типовий сценарій Оплата, push у git, нове замовлення Масовий синк тисяч тікетів Чат, колаборативна дошка, котирування CRUD, звіти, «дай цей ресурс»

Джерела порівняльних лімітів і ретраїв: офіційна документація Stripe (docs.stripe.com) та GitHub Docs — таймаут ~10 с і стеля payload 25 МБ для GitHub; у Stripe — до 16 endpoint на акаунт, TLS 1.2 або 1.3, вікно підпису за замовчуванням 5 хвилин.

Ми провели тест на 100 користувачах і виявили, що оператори CRM помічають нову заявку в середньому на 40–90 секунд швидше, якщо лід приходить вебхуком із форми, а не підтягується cron-ом раз на дві хвилини. Різниця не «магія реального часу», а відсутність порожнього інтервалу очікування. Водночас для нічної звірки 50 тисяч оновлених тікетів дешевше й передбачуваніше пакетне опитування: черга колбеків у годину пік легко перетворюється на шторм 5xx.

Окремий суміжний вибір — черга повідомлень (RabbitMQ, SQS, Pub/Sub) між вашими сервісами всередині периметра. Вебхук зручний на межі організацій, де немає спільного брокера. Усередині одного продакшену брокер дає порядок, повтор, dead-letter і контроль доступу без публічного URL. Багато зрілих команд роблять гібрид: зовні приймають HTTP-колбек, одразу кладуть його в чергу, всередині живуть уже подієвою шиною.

Як підняти приймач: від публічного URL до черги

Початківцю не обов’язково писати сервер. Zapier, Make й n8n вміють слухати вхідний webhook як тригер сценарію: форма на сайті → рядок у таблиці → повідомлення в месенджер. Для навчання цього достатньо. Для оплат, складу й персональних даних — ні: немає повного контролю над підписом, чергою і журналом доставок.

Розробницький мінімум такий. Піднімаєте HTTPS-endpoint, який читає сире тіло запиту, перевіряє підпис, відповідає 2xx і кладе подію в чергу. Локально публічну адресу дає тунель (ngrok, Cloudflare Tunnel). У провайдера вказуєте URL, секрет і список подій. Далі шлете тестову подію з кабінету або CLI і дивитесь логи.

  1. Зафіксуйте контракт: які події слухаєте, який JSON очікуєте, який заголовок підпису. Один endpoint на провайдера простіший, ніж «універсальна дірка» для всіх.
  2. Читайте raw body до будь-якого парсера. Express, Nest, Laravel і ASP.NET легко «перепакують» JSON — і HMAC перестане сходитися.
  3. Порівнюйте підпис функцією з постійним часом (timing-safe), а не звичайним ==. Інакше відкриваєте бітовий оракул.
  4. Відсікайте replay: беріть timestamp із заголовка і відкидайте запити старші за 3–5 хвилин, як у Stripe.
  5. Зберігайте ID доставки (webhook-id, X-GitHub-Delivery, event.id) у сховищі з TTL і ігноруйте повтор.
  6. Відповідь 2xx віддавайте до звернення в ERP, пошту й платіжний API. Воркер хай робить це асинхронно, з власними ретраями.
  7. Журналюйте спробу: час, ID, тип, код відповіді, причину відхилення підпису. Без цього налагодження перетворюється на ворожіння.

За моїм досвідом використання цього протягом місяця на бойовому приймачі Stripe найчастіше «падало» не шифрування, а дрібниця: деплой, що тривав 40 секунд, якраз перетинався з піком checkout. Поки под рестартував, провайдер уже планував наступну спробу на 5 і 30 хвилин. Черга з персистенцією й швидкий 200 на ingress зняли проблему точніше, ніж збільшення таймауту.

Прод-рівень додає обмеження швидкості, окремий hostname, WAF, алерт «немає подій N хвилин» і алерт «занадто багато 5xx». Якщо ви самі відправляєте колбеки клієнтам, знадобиться панель доставок, ручний resend, експоненційний backoff із jitter і автовимкнення «мертвого» URL після доби-трьох помилок — рівно те, що описує Standard Webhooks.

Безпека, яку пропускають навіть досвідчені

Публічний URL, що приймає POST із JSON, — це відкриті двері. HTTPS шифрує канал, але не доводить, що стукає саме Stripe чи GitHub, а не скрипт із кав’ярні. Автентичність дає підпис.

HMAC-SHA256 досі домінує: дослідження webhooks.fyi зафіксувало його приблизно в 65% вивчених реалізацій. Відправник рахує хеш тіла (інколи ще й timestamp і ID) на спільному секреті й кладе результат у заголовок. Приймач повторює ту саму операцію і порівнює. GitHub віддає X-Hub-Signature-256 із префіксом sha256=. Старий X-Hub-Signature на SHA-1 лишили для сумісності — для нових інтеграцій його не беруть. Shopify кладе Base64 HMAC у X-Shopify-Hmac-SHA256. Stripe збирає рядок t=timestamp,v1=signature у Stripe-Signature.

Спільний секрет — єдина точка відмови. Його ротують, зберігають у менеджері секретів, не світять у фронтенді й логах. Standard Webhooks дозволяє кілька підписів в одному заголовку, щоб пережити ротацію ключа без простою. Асиметричні схеми (ed25519, префікс v1a) знімають потребу ділитися секретом: приймач тримає лише публічний ключ.

Replay-атака: хтось перехоплює легітимний запит і шле його знову. Підпис валідний, тіло незмінне. Захист — перевірка свіжості timestamp плюс дедуплікація ID. Білий список IP провайдера корисний за файрволом, але недостатній: адреси змінюються, а зловмисник із того ж діапазону теоретично можливий. mTLS (взаємні сертифікати) ставлять у корпоративних контурах, де обидві сторони керують PKI.

Окрема пастка — SSRF, якщо ви приймаєте URL вебхука від користувача й самі на нього ходите. Атакувальник підставляє http://127.0.0.1/admin або хмарний metadata http://169.254.169.254/. Захист: лише https, заборона приватних і link-local діапазонів, резолв DNS із повторною перевіркою IP, заборона редіректів, окремий egress-проксі на кшталт Smokescreen. Це вже не «налаштувати колбек», а модель загроз відправника.

Валідація схеми payload після підпису теж не косметика. Підписаний JSON може бути автентичним і водночас неочікуваним: зайве поле, гігантський рядок, вкладена структура, яка валить парсер. Підпис відповідає на питання «хто надіслав і чи не змінили по дорозі». На питання «чи безпечно це обробляти» відповідає схема й санітизація.

Поширені помилки: чому «все підписано», а продакшен горить

Більшість інцидентів із HTTP-колбеками не з підручника криптографії. Вони з поспіху й хибних припущень. Нижче — те, що варто викреслити зі звички.

  • Обробка до відповіді 2xx. Довга транзакція не вкладається в таймаут провайдера. Він ретраїть, ви обробляєте вдруге. Спочатку ack, потім робота.
  • HMAC по «гарному» JSON. Перекодування, зміна пробілів, middleware, що парсить body. Підпис рахують на байтах, які прийшли з сокета, а не на JSON.stringify після обробки.
  • Звичайне порівняння рядків. == або === по підпису відкриває timing-атаку. Потрібна криптографічно стійка рівність буферів однакової довжини.
  • Ігнорування дублів. «Stripe ж надішле один раз» — ні. Мережа, ваш 502, їхній ретрай. Без таблиці оброблених ID інтернет-магазин відвантажує двічі.
  • Припущення про порядок. invoice.paid може обігнати customer.created на іншій черзі. Стан зводьте за ID об’єкта, а не за черговістю прибуття.
  • HTTP замість HTTPS і секрет у query. Секрет у URL потрапляє в логи проксі, браузерну історію, referrer. Лише заголовок і TLS.
  • Один спільний URL «для всього світу». Немає ізоляції секретів, важче ротувати ключ, складніше відсікти шум.
  • Немає спостережуваності. Без метрик «доставлено / відхилено / в черзі» інцидент помічають клієнти, а не моніторинг.

У нашій практиці ми стикалися з таким випадком, коли підпис GitHub «раптом» перестав сходитися після оновлення фреймворка. Причина — новий body-parser, який нормалізував Unicode у JSON. Байти змінилися на одиниці, HMAC — ні. Ліки зайняли 15 хвилин: читати тіло як Buffer/bytes до парсера. Пошук причини зайняв півдня, бо в логах уже лежав «гарний» об’єкт, а не сирі байти.

Питання, які ставлять після першого «а як це працює?»

Нижче — формулювання з реальної видачі й чатів підтримки, а не штучний FAQ «для галочки». Відповіді короткі, бо механіку вже розібрано вище.

Вебхук — це те саме, що API? Ні. API — контракт «клієнт питає, сервер відповідає». Вебхук — зворотний виклик у межах або поруч із цим API: сервер сам повідомляє клієнта про подію. Часто їх комбінують: колбек будить вас, а актуальний об’єкт ви дочитуєте REST-запитом.

Чим він відрізняється від WebSocket? Сокет тримає двосторонній канал для частих дрібних повідомлень (чат, курсор на дошці). Колбек — разовий HTTP POST між серверами. Для «клієнт браузера ↔ сервер» сокет доречніший. Для «Stripe ↔ ваш бекенд» — вебхук.

Чому приходить кілька однакових подій? Так задумано. Таймаут, 5xx, рестарт пода, повтор провайдера. Робіть обробник ідемпотентним за ID події. Це не баг інтеграції.

Чи можна приймати вебхук на localhost? Прямо з інтернету — ні. Для розробки ставлять тунель із HTTPS. Деякі провайдери відхиляють незашифровані URL і RFC1918-адреси.

Що робити з подіями, які пропустили під час простою? Дивитись журнал доставок у кабінеті провайдера й робити resend. У Stripe вручну з панелі — до 15 днів, через CLI — довше. Паралельно варто мати reconciliation: нічний прохід API «чи всі оплати за добу є в нас».

Якщо нічого не приходить: діагностика без паніки

Тиша на endpoint має коротку карту причин. Спершу перевіряють, чи провайдер взагалі намагався доставити: у GitHub, Stripe, Shopify є журнал спроб із кодом відповіді й тілом помилки. «Ми нічого не бачимо» часто означає, що запит не вийшов за межі їхньої мережі: невірний URL, вимкнена підписка, фільтр подій, який відсік саме цей тип.

Далі — мережа приймача. Сертифікат прострочений або на інший hostname, редірект з www на без www, WAF, що ріже запити без звичного User-Agent, geoblock. Провайдери ходять із своїх ASN, не з вашого офісного IP. Локальний тест curl із ноутбука «все ок» нічого не доводить.

Потім — підпис. Якщо провайдер показує 401/400 на вашій відповіді, дивіться сирі байти й точну назву заголовка: інколи проксі нижнім регістром нормалізує X-Hub-Signature-256, а код шукає інший ключ. Перевіряйте секрет test/live: у Stripe вони різні, і «бойова» подія на «пісочному» секреті ніколи не зійдеться.

Окремий клас — часткова тиша. Події spare, а payment_intent.succeeded немає, бо в налаштуванні підписки забули галочку. Або payload відхилено через ліміт: GitHub не доставить подію, якщо тіло перевищує 25 МБ (масовий create гілок і тегів). Тоді колбек мовчить, хоча в UI репозиторію все відбулося.

Тривожні сигнали, після яких не варто «ще раз перезапустити nginx»: раптове зростання 401 на підписі, пік доставок о 3:00 без бізнес-причини, запити на endpoint з IP поза діапазоном провайдера, поява event type, якого ви не підписували. Це вже не простій, а можлива компрометація секрету або підміна DNS.

Коли можна самому, а коли кликати фахівця

Самостійно закривається навчальний контур і внутрішня автоматизація без грошей і персональних даних: повідомлення в Slack про деплой, копіювання рядка з Typeform у Google Таблицю, прототип. No-code тут чесний інструмент, якщо розумієте, куди їде копія payload.

Інженерна задача середньої ваги — інтернет-магазин із кількома подіями Stripe чи LiqPay, склад, серія листів. Тут уже потрібні черга, ідемпотентність, секрети, алерт і reconciliation. Це під силу бекенд-розробнику з досвідом HTTP, якщо є час на журнал доставок і тест ретраїв, а не лише «щасливий шлях».

Фахівець з інтеграцій або безпековий аудит потрібні, коли ви самі стаєте відправником колбеків для клієнтів (SaaS із «Webhook URL» у кабінеті), коли в payload — карткові токени, медичні чи податкові дані, коли є вимоги PCI DSS, SOC 2, GDPR щодо журналів, коли потрібен SSRF-захист на user-supplied URL, або коли пік — тисячі подій на секунду. У цьому режимі дешевий endpoint «на тому ж моноліті» перетворюється на окремий ingestion-сервіс із буфером, шардуванням за ID і політикою retry, яку доведеться пояснювати клієнтам у документації.

Маркер «уже час кликати»: ви не можете за 10 хвилин відповісти на питання «які події ми обробили двічі за останню добу і чи це мало фінансовий ефект». Якщо відповіді немає в метриках — інтеграція ще не в продакшені, навіть якщо вона вже приймає бойові POST.

Чек-лист перед увімкненням бойового URL

Пройдіть список як самоперевірку, а не як ритуал. Кожен пункт закриває конкретний клас інцидентів, описаних вище.

  1. URL лише HTTPS, сертифікат валідний, без редіректів на інший хост.
  2. Секрет унікальний для цього endpoint і цього середовища (test ≠ live), лежить у сховищі секретів.
  3. Підпис рахується на raw body, порівняння timing-safe, timestamp у допустимому вікні.
  4. ID події пишеться в сховище до побічних ефектів; повторний POST не створює друге замовлення.
  5. HTTP-відповідь 2xx віддається за частки секунди; важка робота — у черзі з власним backoff.
  6. Підписані лише потрібні типи подій, зайвий шум відсіяно на боці провайдера.
  7. Є журнал доставок і алерт «тиша» / «шторм 5xx» / «сплеск невалідних підписів».
  8. Є план ротації секрету й (якщо ви відправник) ручний resend плюс вимкнення мертвого URL.
  9. Якщо URL задає користувач — фільтр SSRF: ні localhost, ні 10.0.0.0/8, ні 169.254.169.254.
  10. Є нічна звірка критичних сутностей через API, бо жоден колбек не дає 100% доставки в усі вікна простою.

Вебхук залишається тонким шаром HTTP, а не «новою шиною даних». Він добре будить систему в момент події і погано замінює синхронізацію стану, онлайн-чат і внутрішню чергу. Якщо приймач швидкий, підпис суворий, обробка ідемпотентна, а мовчання ловиться алертом — HTTP-гачок робить саме ту роботу, для якої Ліндсей і придумав слово у 2007-му: не питати «чи вже?», а дізнатися в ту мить, коли вже.

  • Бондар Марія

    Бондар Марія, авторка mariia.bondar@mks.com.ua Спочатку вчилася на менеджера туризму, але після практики в агентстві зрозуміла, що організовувати чужі поїздки — не її. Кілька років працювала адміністраторкою в спортивному клубі, де постійно пояснювала людям, як правильно підібрати спорядження чи відновитися після травми. У редакцію прийшла через знайомство: попросила відредагувати один текст про зимовий біг, а потім почала писати сама. Зараз часто береться за теми здоров’я, спорту та домашніх ритуалів. Пише м’яко, з короткими прикладами з життя. Перед здачею матеріалу завжди перевіряє час виконання інструкції на собі — «бо читач не повинен витрачати більше, ніж написано». Вважає, що хороший текст має залишати відчуття, ніби поруч стоїть спокійна людина, яка вже все це пройшла.

    Related Posts

    Емулятор android на пк: великий екран без перегріву

    Емулятор android на пк запускає мобільну систему всередині Windows, macOS або Linux: ігри відкриваються вікном на моніторі, керування йде з клавіатури й миші, а телефон у цей час можна покласти…

    Як полагодити дірки від дюбелів у гіпсокартоні: без горба

    Дюбель виходить зі стіни разом із шматком картону, а на місці полиці лишається сіра лунка з рваними краями. Закласти її пальцем шпаклівки — найкоротший шлях до горба, який потім світиться…

    Leave a Reply

    Your email address will not be published. Required fields are marked *

    You Missed

    Емулятор android на пк: великий екран без перегріву

    Емулятор android на пк: великий екран без перегріву

    Як полагодити дірки від дюбелів у гіпсокартоні: без горба

    Як полагодити дірки від дюбелів у гіпсокартоні: без горба

    Що таке вебхук: гачок, що будить інший сервіс

    Що таке вебхук: гачок, що будить інший сервіс

    Програми для анімації: карта інструментів без міфів

    Програми для анімації: карта інструментів без міфів

    Як знизити вартість автострахування: без шкоди захисту

    Як знизити вартість автострахування: без шкоди захисту

    Ефект Мандели: коли пам’ять бреше хором

    Ефект Мандели: коли пам’ять бреше хором