Сьогодні реальний зв'язок у режимі реального часу — це не просто бажання, а необхідність. Користувачі втомилися чекати оновлення інформації під час повної перезавантаження сторінки. Тут на сцену виходять вебхуки — незаслужено забуті герої технологічного світу. Хоч їх і не часто обговорюють, ситуація змінюється. Я прагну створити найкращий гід з вебхуків у Laravel, щоб це був головний ресурс для інтеграції вебхуків у вашому наступному Laravel-додатку.
Давайте поринемо у яскравий світ вебхуків! Я уявляю вебхуки як цифрових посланців Інтернету, які дозволяють додаткам передавати в режимі реального часу дані іншим додаткам, оновлюючи їх, коли трапляються конкретні події. Чи то подія user registered
, чи payment processed
, вебхуки тримають ваші додатки в синхроні. Вони є ключовим елементом для синхронізації важливих подій. Дізнайтеся більше у цьому чудовому блозі.
Що за магія створює вебхук? Це схоже на стандартний HTTP-запит, але з трішки більше стилю! Зазвичай це HTTP POST запит, оскільки вебхуки повинні відправляти дані. Проте ось що робить їх особливими: включення X-Signature
або X-Hub-Signature
, які гарантують, що дані не були підроблені, зберігаючи їх надійними!
Посилюйте безпеку вашого додатку та захистіть дані за допомогою HTTPS! Не дозволяйте хитрим зловмисникам захоплювати чутливу інформацію, яку ви ділите з клієнтами. Час оновити систему і скористатися можливостями HTTPS!
Коли вебхук досягає вашого додатку, починається шоу! Перш ніж обробляти його, потрібно перевірити джерело — обробляйте лише ті вебхуки, які ви знаєте та довіряєте. Зазвичай ваш вебхук приходитиме з підписаним ключем. Подумайте про це як про таємний потиск руки. Ви можете розшифрувати X-Signature
або X-Hub-Signature
, і вони мусять точно відповідати відправленому payload. Якщо є невідповідність між значенням та розшифрованим заголовком — зупиніться. Цей вебхук було підроблено, і далі не йдіть. Давайте будемо обережними!
Зараз розберемося, як ефективно інтегрувати вебхуки в додатку Laravel. Готові? Поїхали! 🚀
Route::post(
'ingress/github',
AcceptGitHubWebhooks::class,
)->name('ingress:github');
Ми налаштували маршрут для ingress/github
, який стане нашим потужним інструментом для управління всіма вебхуками, що надходять з GitHub. Налаштуйте це у параметрах вашого репозиторію GitHub у розділі "Webhooks". Додаючи цей вебхук, ви можете обрати заголовок content-type і підпис, щоб оптимізувати підпис запиту. Тепер готові приймати запити, підтримувані надійним контролером для управління всіма діями. Для додаткової безпеки включіть проміжне програмне забезпечення до маршруту для покращення перевірки джерела і payload.
Давайте запустимо команду Artisan, щоб створити проміжне програмне забезпечення, яке підсилить нашу перевірку запитів, ретельно перевіряючи джерело та вміст.
php artisan make:middleware VerifyGitHubWebhook
Після створення оновимо визначення маршруту.
Route::post(
'ingress/github',
AcceptGitHubWebhooks::class,
)->name('ingress:github')->middleware([
VerifyGitHubWebhook::class,
]);
Продовжимо до нашого проміжного кодексу для перевірки вхідного вебхука.
final class VerifyGitHubWebhook
{
public function handle(Request $request, Closure $next): Response
{
if (! $this->isValidSource($request->ip())) {
return new JsonResponse(
data: ['message' => 'Недійсне джерело IP.'],
status: 403,
);
}
$signature = $request->header('X-Hub-Signature-256');
$secret = config('services.github.webhook_secret');
if ( ! $signature || ! $this->isValidSignature(
$request->getContent(),
$signature,
$secret,
)) {
return new JsonResponse(
data: ['message' => 'Недійсна підпис.'],
status: 403,
);
}
return $next($request);
}
private function isValidSignature(
string $payload,
string $signature,
string $secret,
): bool {
return hash_equals(
'sha256=' . hash_hmac('sha256', $payload, $secret),
$signature
);
}
private function isValidSource(string $ip): bool {
$validIps = cache()->remember(
key: 'github:webhook_ips',
ttl: 3600,
callback: fn () => Http::get(
url: 'https://api.github.com/meta',
)->json('hooks', []),
);
return in_array($ip, $validIps, true);
}
}
Зараз давайте розглянемо наше проміжне програмне забезпечення. Спочатку ми витягуємо адресу IP джерела з запиту і перевіряємо її проти IP-адрес, які отримуємо з API GitHub. Для підвищення ефективності можна використати кешування, не забуваючи час від часу оновлювати кеш. Далі отримуємо заголовок підпису та ключ підпису з конфігурацій нашого додатку. Час взятись за перевірку, чи збігається хешований варіант нашого запиту з наданою підписю! Якщо все вірно, то GitHub довіряє нам своїми webhook-даними.
Тепер розгляньмо код контролера, щоб розкрити потенціал цього вебхука! Пам'ятайте про девіз: Перевіряти - Ставити в чергу - Відповідати! Ми вже підтвердили джерело, тож що далі? Час нашому контролеру відправити фонове завдання з вхідним payload.
final class AcceptGitHubWebhooks
{
public function __construct(private Dispatcher $bus) {}
public function __invoke(Request $request): Response
{
defer(
callback: fn() => $this->bus->dispatch(
command: new HandleGitHubWebhook(
payload: $request->all(),
),
),
);
return new JsonResponse(
data: ['message' => 'Webhook прийнято.'],
status: Response::HTTP_ACCEPTED,
);
}
}
Завдання контролера — швидко передати завдання HandleGitHubWebhook
в чергу та негайно повернути відповідь, щоб відправник знав, що все пройшло успішно. У цей момент нічого не повинно заважати вашому процесу вебхука: якщо ви отримуєте раптовий наплив вебхуків, ваша черга готова все впорати, або ви можете виділити додаткових робітників для управління завданнями. Ми обгорнули до Dispatching тут за допомогою допоміжного модуля Laravel 11, який чекає на відповідь перед виконанням цього завдання. Кажучи просто, це мікро-оптимізація, але досить корисна.
Наш контролер у відмінному стані, але на цьому не зупиняємося. Нам потрібно виконати завдання обробки вхідного payload. Коли вебхук надходить до нас, він приходить у вигляді JSON-об’єкта з властивістю event
. Далі ми зв'язуємо цю назву події з нашою динамічною внутрішньою бізнес-логікою. Заглибимось у HandleGitHubWebhook
завдання і подивимось, як це реалізувати.
final class HandleGitHubWebhook implements ShouldQueue
{
use Queueable;
public function __construct(public array $payload) {}
public function handle(GitHubService $service): void
{
$action = $this->payload['action'];
match ($action) {
'published' => $service->release(
payload: $this->payload,
),
'opened' => $service->opened(
payload: $this->payload,
),
default => Log::info(
message: "Неперехоплене дію вебхука: $action",
context: $this->payload,
),
};
}
}
Ми використовуємо інтерфейс ShouldQueue
, сигналізуючи Laravel, що цей клас варто обробити особливим чином. Ін'єктуємо адаптер Queueable
, щоб поліпшити поведінку черги. Звісно, ви можете перевизначити методи цього адаптера, якщо це знадобиться, але за 8 років роботи з Laravel, я жодного разу не стикався з такою необхідністю. Конструктор приймає payload, надісланий контролером, затверджений як публічне поле. Коли це завдання ставиться в чергу, воно не може бути відновлено з приватними полями. Врешті-решт, з’являється метод handle — важливий функціонал, що активується для виконання цього завдання в черзі. І, як вам відомо, можна використовувати ін'єкцію залежностей у методі, якщо бажаєте управляти бізнес-логікою.
Я зазвичай створюю клас-сервіс для всієї логіки обробки вебхуків з кожного джерела. Розглянемо сервіс в app/Services/GitHubService
.
final class GitHubService
{
public function __construct(private DatabaseManager $database) {}
public function release(array $payload): void
{
$this->database->transaction(function () use ($payload) {
// Логіка обробки публікації релізу
});
}
public function opened(array $payload): void
{
$this->database->transaction(function () use ($payload) {
// Логіка обробки відкритої ПР
});
}
}
Визначаючи конкретний метод для кожної дії вебхука, ми підтримуємо порядок у сервісному класі. Тепер ми можемо ефективно управляти усіма бажаними вебхуками з GitHub, спрощуючи подальше розширення — без впливу на основну бізнес-логіку.
Важливо пам'ятати, що хоча в цій статті ми розглянули лише вебхуки GitHub, ті ж принципи можуть бути застосовані до будь-якого вебхука, який ви хочете впровадити. Процес полягає в захопленні запиту через наш маршрутизатор, а потім маршрутизації його до контролера через проміжне програмне забезпечення. Це проміжне програмне забезпечення перевіряє джерело та payload, повертаючи коректну помилку за потреби, і, зрештою, передає до контролера. Завдання контролера — передати дані вебхука у фоновий режим якомога швидше, щоб не було затримок у відповіді на запит. Після повернення відповіді ми можемо обробити вебхук на робочому потоці у фоновому режимі, залишаючи основний додаток вільним для прийому нових запитів без затримок у обробці вебхуків.
Але є оберти! Як ми можемо бути впевнені, що не пропускаємо жодного вебхука? Як гарантувати, що GitHub повторно надішле вебхуки, якщо перша спроба не вдасться? У нас немає спостережливості, ми не знаємо, що може зісковзнути у нас з рук — або який наш коефіцієнт помилок. Не отримуючи сповіщень, ми залишаємося в невіданні про будь-які невдачі — і це викликає у мене занепокоєння!
Hookdeck — ваш ідеальний союзник у керуванні вебхуками! Налаштуйте, моніторте та спостерігайте за всіма вебхуками в одному потужному сервісі. Забудьте про плутанину з кількома джерелами — Hookdeck спростить вам завдання. Потрібно скинути підпис або повторити вебхук? Робіть це без зусиль за допомогою фантастичних фільтрів і продуманої маршрутизації. Скажіть прощавай заплутаній логіці та привітайте контроль над вебхуками!
За допомогою Hookdeck вам більше не потрібно займатися фоновими задачами — це ваш універсальний сервіс для обробки вебхуків! Він надсилає та повторно намагається доставити вебхуки, направляючи їх у декілька цілей, як ваш API та AWS S3. Hookdeck перевіряє джерело і вміст кожного вебхука за вас, тому все, що вам потрібно — це відкрити свій HTTP-запит, випити кави і насолоджуватися магією!