Як тестувати вебхуки GitHub
Налаштовуємо вебхук репозиторію, розбираємо заголовки GitHub, правильно перевіряємо X-Hub-Signature-256 і повторно доставляємо події під час налагодження.
Оновлено:
Вебхуки GitHub повідомляють ваш сервіс про push, pull request, issues, релізи та інші події. Перш ніж писати обробник, корисно побачити справжню доставку: які заголовки ставить GitHub, як виглядає тіло для різних content type і як формується підпис. Покажемо, як ловити доставки на публічну адресу, перевіряти їх і пересилати на локальний код.
Крок 1: створіть адресу для перехоплення
Відкрийте Webhook Inspector і створіть адресу, наприклад:
https://webhook.voronyuk.com/abcd23efgh/github/webhook
Вона приймає будь-який метод, підшлях і query. За замовчуванням відповідає 200 з {"ok":true,"request_id":N}, тож GitHub позначає кожну доставку як успішну.
Крок 2: додайте вебхук у репозиторій
- У репозиторії відкрийте Settings → Webhooks і натисніть Add webhook.
- Payload URL: вставте адресу перехоплювача.
- Content type:
application/jsonабоapplication/x-www-form-urlencoded(про різницю нижче). - Secret: довгий випадковий рядок. Ним ви перевірятимете підписи.
- Виберіть, які події надсилати: лише
push, усі або окремі. - Залиште позначку Active і натисніть Add webhook.
GitHub одразу надішле подію ping, і за кілька секунд вона з’явиться в інспекторі.
Крок 3: заголовки, які варто знати
| Заголовок | Що означає |
|---|---|
X-GitHub-Event |
Назва події: push, pull_request, ping тощо |
X-GitHub-Delivery |
Унікальний GUID цієї доставки |
X-GitHub-Hook-ID |
ID вебхука, який її надіслав |
X-Hub-Signature-256 |
sha256= і далі HMAC-SHA256 від сирого тіла в hex |
X-Hub-Signature |
Старий підпис SHA-1, лишився для сумісності |
User-Agent |
Починається з GitHub-Hookshot/ |
Маршрутизуйте за X-GitHub-Event, а за X-GitHub-Delivery відсіюйте дублікати.
Подія ping
Перша доставка — це ping. У тілі є рядок zen, hook_id і конфігурація hook. Обробник має відповісти на неї 2xx, навіть якщо нічого з нею не робить.
JSON чи form-encoded
З application/json тіло — це сам JSON. З application/x-www-form-urlencoded JSON передається як значення поля форми payload. Інспектор розкодовує form data, тож поле payload з JSON усередині ви побачите в будь-якому разі. З JSON зазвичай простіше.
В обох випадках підпис рахується від сирих байтів тіла, а не від розкодованого JSON.
Крок 4: перевірка X-Hub-Signature-256
GitHub рахує HMAC-SHA256 від сирого тіла з вашим секретом і надсилає результат як sha256=<hex>. Порахуйте те саме і порівняйте за сталий час.
Node.js
const crypto = require('crypto');
const express = require('express');
const SECRET = process.env.GITHUB_WEBHOOK_SECRET;
const app = express();
function isValid(rawBody, header) {
if (!header || !header.startsWith('sha256=')) return false;
const expected =
'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
const a = Buffer.from(expected);
const b = Buffer.from(header);
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
app.post('/webhook', express.raw({ type: '*/*' }), (req, res) => {
if (!isValid(req.body, req.get('X-Hub-Signature-256'))) {
return res.status(401).send('Bad signature');
}
const event = req.get('X-GitHub-Event');
const delivery = req.get('X-GitHub-Delivery');
res.status(202).send('Accepted'); // спершу відповідаємо
console.log(event, delivery); // потім обробляємо (краще в черзі)
});
app.listen(3000);
Приклад розрахований на application/json. Якщо обрали form encoding, після перевірки підпису розберіть поле payload із сирого тіла.
Python
import hashlib
import hmac
import os
from flask import Flask, abort, request
app = Flask(__name__)
SECRET = os.environ["GITHUB_WEBHOOK_SECRET"].encode()
@app.post("/webhook")
def webhook():
header = request.headers.get("X-Hub-Signature-256", "")
expected = "sha256=" + hmac.new(SECRET, request.get_data(), hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, header):
abort(401)
print(request.headers.get("X-GitHub-Event"), request.headers.get("X-GitHub-Delivery"))
return "", 202
if __name__ == "__main__":
app.run(port=3000)
Крок 5: пересилання на localhost
У панелі Пересилання інспектора вкажіть ціль http://localhost:3000. Перехоплений підшлях дописується до цілі, тож .../abcd23efgh/github/webhook прийде на http://localhost:3000/webhook. Можна скористатися relay через браузер (сервер має дозволяти CORS) або CLI-агентом, якому браузер не потрібен і який доганяє пропущені запити:
WI_TOKEN='<токен>' inspector listen https://webhook.voronyuk.com/abcd23efgh/github --to localhost:3000
Точну команду з токеном скопіюйте з панелі. Детальніше — у гайді як тестувати вебхуки локально.
Тайм-аути, збої й повторна доставка
- Відповідайте за 10 секунд. GitHub чекає
2xxпротягом 10 секунд. Якщо сервер не встиг, GitHub рве з’єднання і записує доставку як невдалу. Спершу підтвердіть отримання, а обробляйте асинхронно. - Автоматичних повторів немає. GitHub не надсилає невдалі доставки повторно сам. Якщо ендпоінт лежав, повторювати доведеться вручну або через REST API.
- Recent Deliveries. У Settings → Webhooks відкрийте вебхук і перейдіть на вкладку Recent Deliveries. Для кожної доставки видно заголовки й payload запиту та відповідь вашого сервера. Кнопка Redeliver надсилає її ще раз.
Оскільки невдалі доставки не повторюються, власна копія не завадить. Кожна доставка на адресу перехоплювача зберігається, і її можна скільки завгодно разів повторити з тестера вебхуків GitHub на локальний сервер. Щоб перевірити реакцію на збої, задайте для адреси власну відповідь, наприклад статус 500, і подивіться, що GitHub запише в Recent Deliveries.
Схожі гайди: вебхуки Stripe і Slack Events API.
Часті питання
Як протестувати вебхук GitHub?
Додайте вебхук у Settings → Webhooks, указавши як Payload URL публічну адресу перехоплювача. GitHub одразу надішле ping. Далі зробіть push чи відкрийте pull request, щоб отримати справжні події, і роздивіться або повторіть їх.
Чому не сходиться підпис вебхука GitHub?
Найімовірніше, ви хешуєте вже розпарсене або перезібране тіло. Рахуйте HMAC від сирих байтів, порівнюйте разом із префіксом sha256= і перевірте, що секрет збігається до символу, без пробілів у кінці.
Чи повторює GitHub невдалі доставки вебхуків?
Ні. GitHub не повторює невдалі доставки автоматично. Скористайтеся кнопкою Redeliver у Recent Deliveries або REST API.
Який тайм-аут у вебхуків GitHub?
10 секунд. Якщо за цей час сервер не відповів 2xx, доставка вважається невдалою.
Що обрати: application/json чи form-encoded?
application/json простіший: тіло і є payload. При form encoding JSON приходить у полі payload, і його треба спершу розкодувати.
Спробуйте зараз
Отримайте безкоштовний URL для вебхуків і дивіться, як приходять запити, — без реєстрації.
Отримати URL для вебхуків