Представьте: ваш интернет-магазин обрабатывает 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-системы под ваш проект.







