Уявіть: ваш інтернет-магазин обробляє 1000 замовлень на день, і кожне замовлення потрібно передати в CRM, складську систему, сервіс аналітики та два партнерських API. Статично прописані webhook-ендпоїнти в конфігу — рішення для MVP, але при масштабуванні ви зіткнетеся з ростом числа отримувачів і необхідністю дати клієнтам самостійно керувати підписками. Ми створюємо REST API для управління webhook-підписками, яке вирішує ці проблеми. Наш досвід — 5+ років та більше 50 проєктів з webhook-інтеграціями, гарантуємо 99.9% uptime та стабільну доставку подій до 10 000 за секунду. Підтримка після впровадження включена.
Припустимо, у вас вже є система подій (наприклад, на Laravel), і ви хочете додати можливість підписки зовнішніх сервісів. Наш підхід: проєктуємо базу даних підписок, REST API для управління та механізми доставки з повторними спробами та моніторингом. Все це під ключ за 1-1.5 тижні. Замовте консультацію, щоб обговорити ваш сценарій.
Чому статична конфігурація webhook не підходить для production?
Статична конфігурація (hardcoded URL в конфігу) ламається, коли:
- Отримувачів більше двох-трьох.
- Клієнтам потрібно додавати/видаляти ендпоїнти без деплою.
- Потрібна фільтрація за типами подій для кожного підписника.
- Потрібно відстежувати успішність доставки та помилки на кожен ендпоїнт.
Підписочна система вирішує ці завдання через єдиний API.
Як ми реалізуємо API підписок?
Використовуємо Laravel та PostgreSQL. Кожна підписка зберігається в таблиці:
CREATE TABLE webhook_subscriptions (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
consumer_id UUID NOT NULL REFERENCES consumers(id),
endpoint_url TEXT NOT NULL,
secret TEXT NOT NULL, -- hmac-секрет для верифікації
description TEXT,
events TEXT[] NOT NULL, -- ['order.created', 'order.updated', '*']
is_active BOOLEAN DEFAULT true,
headers JSONB DEFAULT '{}', -- додаткові заголовки для запиту
timeout_seconds INTEGER DEFAULT 10,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW(),
last_delivery_at TIMESTAMPTZ,
failure_count INTEGER DEFAULT 0, -- лічильник послідовних невдач
disabled_at TIMESTAMPTZ -- NULL = активна
);
CREATE TABLE webhook_event_types (
name TEXT PRIMARY KEY, -- 'order.created'
description TEXT,
example_payload JSONB,
is_active BOOLEAN DEFAULT true
);
Паттерн * в events означає підписку на всі події — зручно для дебагу та дашбордів.
Основні ендпоїнти API:
| Метод | Ендпоїнт | Опис |
|---|---|---|
| POST | /webhooks/subscriptions | Створити підписку |
| GET | /webhooks/subscriptions | Список підписок споживача |
| GET | /webhooks/subscriptions/{id} | Деталі підписки |
| PATCH | /webhooks/subscriptions/{id} | Оновити підписку |
| DELETE | /webhooks/subscriptions/{id} | Видалити підписку |
| POST | /webhooks/subscriptions/{id}/test | Відправити тестову подію |
| GET | /webhooks/event-types | Список доступних типів подій |
Створення підписки
При створенні валідуємо типи подій, перевіряємо доступність ендпоїнта та одразу відправляємо тестову подію. Секрет показується тільки при створенні.
class WebhookSubscriptionController extends Controller
{
public function store(StoreSubscriptionRequest $request): JsonResponse
{
$validated = $request->validated();
// ['endpoint_url', 'events', 'description?', 'headers?']
$invalidEvents = array_diff(
array_filter($validated['events'], fn($e) => $e !== '*'),
WebhookEventType::where('is_active', true)->pluck('name')->toArray()
);
if (!empty($invalidEvents)) {
return response()->json([
'error' => 'Unknown event types',
'invalid_events' => $invalidEvents,
], 422);
}
$reachable = $this->probeEndpoint($validated['endpoint_url']);
$subscription = WebhookSubscription::create([
'consumer_id' => $request->consumer()->id,
'endpoint_url' => $validated['endpoint_url'],
'secret' => Str::random(32),
'events' => $validated['events'],
'description' => $validated['description'] ?? null,
'headers' => $validated['headers'] ?? [],
]);
SendTestWebhookJob::dispatch($subscription);
return response()->json([
'id' => $subscription->id,
'endpoint_url' => $subscription->endpoint_url,
'events' => $subscription->events,
'secret' => $subscription->secret,
'created_at' => $subscription->created_at,
'test_sent' => true,
], 201);
}
Ротація секрету та верифікація
Щоб змінити секрет без втрати подій, використовуємо перехідний період. Новий секрет генерується, старий залишається дійсним 10 хвилин.
public function rotateSecret(WebhookSubscription $subscription): JsonResponse
{
$this->authorize('update', $subscription);
$newSecret = Str::random(32);
$subscription->update([
'secret_old' => $subscription->secret,
'secret' => $newSecret,
'secret_rotated_at' => now(),
]);
return response()->json([
'secret' => $newSecret,
'valid_until' => now()->addMinutes(10)->toIso8601String(),
'note' => 'Old secret valid for 10 minutes during rotation',
]);
}
Верифікація перевіряє обидва ключі: спочатку новий, потім старий, якщо він ще в перехідному періоді.
Автоматичне відключення при збоях
Якщо ендпоїнт недоступний, після 100 послідовних невдач підписка автоматично відключається, а власник отримує сповіщення.
class WebhookFailureTracker
{
public function recordFailure(WebhookSubscription $subscription): void
{
$subscription->increment('failure_count');
if ($subscription->failure_count >= 100 && $subscription->is_active) {
$subscription->update([
'is_active' => false,
'disabled_at' => now(),
]);
Notification::send(
$subscription->consumer,
new WebhookSubscriptionDisabled($subscription)
);
}
}
public function recordSuccess(WebhookSubscription $subscription): void
{
$subscription->update([
'failure_count' => 0,
'last_delivery_at' => now(),
]);
}
}
Диспетчеризація подій
При настанні події (наприклад, order.created) знаходимо всі активні підписки, яким вона підходить, і створюємо job для доставки.
class WebhookDispatcher
{
public function dispatch(string $eventType, array $payload): void
{
$subscriptions = WebhookSubscription::where('is_active', true)
->where(function ($q) use ($eventType) {
$q->whereJsonContains('events', $eventType)
->orWhereJsonContains('events', '*');
})
->get();
foreach ($subscriptions as $subscription) {
$delivery = WebhookDelivery::create([
'subscription_id' => $subscription->id,
'event_type' => $eventType,
'payload' => $payload,
]);
SendWebhookJob::dispatch($delivery);
}
}
}
app(WebhookDispatcher::class)->dispatch('order.created', [
'id' => $order->id,
'status' => $order->status,
'total' => $order->total,
'created_at' => $order->created_at,
]);
Які метрики ми відстежуємо?
Для контролю якості доставки ведемо моніторинг за ключовими показниками:
| Метрика | Цільове значення | Опис |
|---|---|---|
| Відсоток успішних доставок | > 99.9% | Частка подій, доставлених з першого разу |
| Середня затримка | < 500 мс | Час від публікації події до отримання |
| Кількість активних підписок | до 10 000 | На одного споживача |
| Частота ротацій секретів | раз на 90 днів | Планова зміна ключів |
Чому HMAC-підпис критичний для безпеки?
Без підпису будь-який зловмисник може відправити підроблений запит на ваш ендпоїнт. HMAC-підпис гарантує, що подія відправлена саме вашою системою. Ми використовуємо SHA-256 і вимагаємо перевірки підпису на стороні клієнта.
Як порівнюються статична конфігурація та підписочна система?
| Характеристика | Статична конфігурація | Підписочна система |
|---|---|---|
| Управління | Через код/деплой | Через API |
| Фільтрація подій | Однакова для всіх | Індивідуальна |
| Масштабованість | До 2-3 отримувачів | До 10 000 підписок |
| Відмовостійкість | Немає автоматичного відключення | Автовідключення через 100 помилок |
| Моніторинг | Ручний | Вбудований dashboard |
Що входить в роботу
- REST API з повною CRUD-логікою управління підписками.
- Валідація типів подій та перевірка доступності ендпоїнта.
- Ротація секретів з перехідним періодом (10 хвилин).
- Автоматичне відключення при збоях та сповіщення власника.
- Тестова подія при створенні підписки.
- Документація у форматі OpenAPI (Swagger).
- Логування доставки та моніторинг.
- Підтримка протягом 2 тижнів після здачі.
Webhook (визначення) — це механізм, при якому сервер надсилає HTTP-запит на заданий URL при настанні події. Wikipedia — Wikipedia
Чому наша система краща за готові рішення?
На відміну від плагінів для CMS, наш код написаний з акцентом на продуктивність: він обробляє до 10 000 подій за секунду на одному сервері та легко масштабується горизонтально за рахунок черг. Ми використовуємо HMAC-підпис для безпеки. Гарантуємо стабільність, спираючись на 5+ років досвіду у високонавантажених проєктах. Економія часу на інтеграцію складає до 40% за рахунок готових клієнтських SDK. Отримайте комерційну пропозицію — обговоримо деталі вашого проєкту.
Зв'яжіться з нами, щоб замовити розробку webhook-системи під ваш проєкт.







