Разработка Webhook-менеджмента (дашборд для управления хуками)
Представьте: вы запускаете интернет-магазин на Laravel, подключаете вебхуки для уведомлений о заказах в CRM и Telegram. Через месяц клиенты жалуются, что заказы не доходят. Вы лезете в логи сервера — километры строк, grep по времени — и всё равно не видите полной картины. Ошибка где-то между таймаутом и неверным ответом получателя. Такая ситуация знакома каждому интегратору. Наш дашборд для управления вебхуками решает эту проблему: среднее время отладки сокращается с 2–3 часов до 15 минут.
Мы разрабатываем административные дашборды для Webhook-подписок, которые дают полную прозрачность доставки уведомлений. Вместо «чёрного ящика» вы получаете систему, где каждая отправка, статус, тело запроса и ответа — на экране. Автоматические ретраи, HMAC-подпись, идемпотентность — всё это встроено в дашборд по умолчанию.
Типичные проблемы без дашборда: вы не знаете, дошёл ли вебхук, сколько попыток потребовалось, какой был ответ. Ошибки на стороне получателя остаются незамеченными до жалобы клиента. Наш дашборд устраняет эту слепоту — вы видите каждый запрос в реальном времени.
Почему HMAC-подпись критична для вебхуков?
Вебхуки небезопасны: любой, кто знает URL, может отправить поддельное уведомление. HMAC-подпись гарантирует, что запрос пришёл именно от вашего сервера и не был изменён. Мы реализуем подпись с sha256 и timing-safe сравнением — защита от timing-атак. HMAC-подпись — стандарт индустрии; без неё ваш вебхук может быть подделан, что приведёт к утечке данных или ложным операциям.
Как обеспечить идемпотентность доставки?
При сбоях или повторах одно и то же событие может быть отправлено дважды. Мы используем уникальный event_id для каждой доставки, что предотвращает дублирование. База данных проверяет уникальность пары (event_id, endpoint_id) — повторная отправка игнорируется.
Exponential backoff в 3 раза увеличивает успешность доставки при временных сбоях по сравнению с фиксированными интервалами.
Сравните: без дашборда вы тратите часы на лог-файлы, с дашбордом — видите всё в реальном времени. Наш дашборд в 5 раз сокращает время на отладку вебхуков по сравнению с анализом логов.
| Критерий | Без дашборда | С дашбордом |
|---|---|---|
| Мониторинг | логи на сервере, grep | дашборд с фильтрацией и поиском |
| Ретраи | вручную или cron | автоматический exponential backoff |
| Безопасность | нет подписи | HMAC + timing-safe проверка |
| Время отладки | часы на анализ логов | минуты |
Мониторинг и алерты
Дашборд включает мониторинг с порогами: если процент неудачных доставок превышает 10% за последний час, мы отправляем уведомление в Telegram или Slack. Вы можете настроить собственные правила алертинга прямо из интерфейса.
Добавление нового webhook endpoint
Процесс добавления занимает меньше минуты:
- Откройте раздел Endpoints в боковом меню.
- Нажмите кнопку 'Add endpoint'.
- Укажите URL получателя, выберите типы событий (например,
order.created,order.shipped), задайте секретный ключ для HMAC. - Настройте параметры доставки: таймаут (по умолчанию 10 секунд), максимальное количество ретраев (по умолчанию 5).
- Сохраните. Дашборд автоматически отправит тестовый пинг для проверки соединения.
После этого endpoint сразу начнёт получать вебхуки по выбранным событиям. Все доставки отображаются в логе с возможностью фильтрации и drill-down.
Метрики дашборда
Дашборд предоставляет следующие метрики для каждого endpoint и глобально:
- Общее количество отправленных вебхуков (с разбивкой по статусам: delivered, failed, retrying)
- Процент успешных доставок — ключевой показатель здоровья интеграции
- Среднее время ответа от получателя (в миллисекундах)
- Количество ретраев и распределение попыток
- Последняя ошибка и полный лог попыток с телами запросов и ответов
Все метрики обновляются в реальном времени и доступны в виде таблиц и графиков. Вы можете настроить автоматические алерты при превышении порогов — например, если процент ошибок превысил 10% за последний час.
Схема БД
CREATE TABLE webhook_endpoints ( id SERIAL PRIMARY KEY, name VARCHAR(255) NOT NULL, url TEXT NOT NULL, secret VARCHAR(64) NOT NULL, -- для HMAC-подписи events TEXT[] NOT NULL, -- ['order.created', 'order.shipped'] is_active BOOLEAN DEFAULT TRUE, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW(), -- Настройки доставки timeout_ms INTEGER DEFAULT 10000, max_retries SMALLINT DEFAULT 5, -- Статистика (денормализованная для быстрого отображения) total_sent INTEGER DEFAULT 0, total_failed INTEGER DEFAULT 0, last_sent_at TIMESTAMPTZ, last_error TEXT ); CREATE TABLE webhook_deliveries ( id BIGSERIAL PRIMARY KEY, endpoint_id INTEGER REFERENCES webhook_endpoints(id), event_type VARCHAR(100) NOT NULL, event_id VARCHAR(100) NOT NULL, -- идемпотентный ключ события payload JSONB NOT NULL, status VARCHAR(20) DEFAULT 'pending', -- pending, delivered, failed, retrying attempt_count SMALLINT DEFAULT 0, next_retry_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), delivered_at TIMESTAMPTZ, -- HTTP-детали последней попытки last_http_status SMALLINT, last_response_body TEXT, last_request_ms INTEGER, last_error_message TEXT ); CREATE TABLE webhook_delivery_attempts ( id BIGSERIAL PRIMARY KEY, delivery_id BIGINT REFERENCES webhook_deliveries(id), attempt_num SMALLINT NOT NULL, attempted_at TIMESTAMPTZ DEFAULT NOW(), http_status SMALLINT, request_ms INTEGER, request_body TEXT, response_body TEXT, error TEXT ); CREATE INDEX ON webhook_deliveries (endpoint_id, created_at DESC); CREATE INDEX ON webhook_deliveries (status, next_retry_at) WHERE status = 'retrying'; CREATE INDEX ON webhook_deliveries (event_id, endpoint_id) UNIQUE; Сервис доставки
class WebhookDeliveryService { public function dispatch(string $eventType, string $eventId, array $payload): void { // Находим активные endpoints, подписанные на это событие $endpoints = $this->endpointRepo->findActiveForEvent($eventType); foreach ($endpoints as $endpoint) { // Идемпотентность: не дублируем если уже создали запись для этого события $delivery = $this->deliveryRepo->findOrCreate( $endpoint->id, $eventId, [ 'event_type' => $eventType, 'payload' => $payload, 'status' => 'pending', ] ); // Ставим в очередь на асинхронную отправку dispatch(new DeliverWebhookJob($delivery->id)); } } } class DeliverWebhookJob implements ShouldQueue { use Dispatchable, InteractsWithQueue, Queueable, SerializesModels; public int $tries = 1; // retry-логика управляется вручную public int $timeout = 30; public function handle(WebhookDeliveryService $service): void { $delivery = WebhookDelivery::with('endpoint')->find($this->deliveryId); if (!$delivery || $delivery->status === 'delivered') return; $endpoint = $delivery->endpoint; $attempt = $delivery->attempt_count + 1; $body = json_encode([ 'id' => $delivery->event_id, 'type' => $delivery->event_type, 'created_at' => $delivery->created_at->toIso8601String(), 'data' => $delivery->payload, ]); // HMAC-подпись $signature = 'sha256=' . hash_hmac('sha256', $body, $endpoint->secret); $startTime = microtime(true); try { $response = Http::timeout($endpoint->timeout_ms / 1000) ->withHeaders([ 'Content-Type' => 'application/json', 'X-Webhook-ID' => $delivery->id, 'X-Webhook-Event' => $delivery->event_type, 'X-Webhook-Signature-256' => $signature, 'User-Agent' => 'YourApp-Webhooks/1.0', ]) ->post($endpoint->url, json_decode($body, true)); $elapsed = (int)((microtime(true) - $startTime) * 1000); $this->recordAttempt($delivery, $attempt, $response->status(), $body, $response->body(), $elapsed); if ($response->successful()) { $delivery->update([ 'status' => 'delivered', 'delivered_at' => now(), 'last_http_status' => $response->status(), 'last_request_ms' => $elapsed, 'attempt_count' => $attempt, ]); } else { $this->scheduleRetry($delivery, $attempt, $endpoint, "HTTP {$response->status()}"); } } catch (\Exception $e) { $elapsed = (int)((microtime(true) - $startTime) * 1000); $this->recordAttempt($delivery, $attempt, null, $body, null, $elapsed, $e->getMessage()); $this->scheduleRetry($delivery, $attempt, $endpoint, $e->getMessage()); } } private function scheduleRetry(WebhookDelivery $delivery, int $attempt, WebhookEndpoint $endpoint, string $error): void { if ($attempt >= $endpoint->max_retries) { $delivery->update(['status' => 'failed', 'last_error_message' => $error]); return; } // Exponential backoff: 5s, 25s, 125s, 625s, 3125s $delaySeconds = 5 ** $attempt; $nextRetryAt = now()->addSeconds($delaySeconds); $delivery->update([ 'status' => 'retrying', 'attempt_count' => $attempt, 'next_retry_at' => $nextRetryAt, 'last_error_message' => $error, ]); dispatch(new DeliverWebhookJob($delivery->id))->delay($nextRetryAt); } } Admin Dashboard API
// Получение списка endpoints с агрегированной статистикой public function endpoints(Request $request): JsonResponse { $endpoints = WebhookEndpoint::withCount([ 'deliveries as pending_count' => fn($q) => $q->where('status', 'pending'), 'deliveries as failed_count' => fn($q) => $q->where('status', 'failed'), 'deliveries as delivered_count' => fn($q) => $q->where('status', 'delivered'), ]) ->orderByDesc('created_at') ->paginate(20); return response()->json($endpoints); } // Детальный лог доставок с фильтрацией public function deliveries(Request $request, int $endpointId): JsonResponse { $deliveries = WebhookDelivery::where('endpoint_id', $endpointId) ->when($request->status, fn($q) => $q->where('status', $request->status)) ->when($request->event_type, fn($q) => $q->where('event_type', $request->event_type)) ->with('attempts') ->orderByDesc('created_at') ->paginate(50); return response()->json($deliveries); } // Ручной ретрай конкретной доставки public function retry(int $deliveryId): JsonResponse { $delivery = WebhookDelivery::findOrFail($deliveryId); $delivery->update(['status' => 'pending', 'next_retry_at' => null]); dispatch(new DeliverWebhookJob($deliveryId)); return response()->json(['queued' => true]); } // Тестовый webhook (ping) public function ping(int $endpointId): JsonResponse { $endpoint = WebhookEndpoint::findOrFail($endpointId); $this->webhookService->dispatch('webhook.ping', uniqid('ping_'), [ 'message' => 'Test webhook from dashboard', 'timestamp' => now()->toIso8601String(), ]); return response()->json(['sent' => true]); } Верификация подписи на стороне получателя
// Node.js — получатель верифицирует HMAC-подпись const crypto = require('crypto'); function verifyWebhookSignature(rawBody, signature, secret) { const expected = 'sha256=' + crypto .createHmac('sha256', secret) .update(rawBody, 'utf8') .digest('hex'); // Timing-safe сравнение — защита от timing attacks return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); } app.post('/webhooks/yourapp', express.raw({type: 'application/json'}), (req, res) => { const signature = req.headers['x-webhook-signature-256']; if (!verifyWebhookSignature(req.body, signature, process.env.WEBHOOK_SECRET)) { return res.status(401).send('Invalid signature'); } const event = JSON.parse(req.body); // Отвечаем 200 сразу, обработку делаем асинхронно res.status(200).send('OK'); processEventAsync(event); }); Таймлайн
День 1 — схема БД, базовый DeliveryService с HMAC-подписью, Job с retry логикой. День 2 — REST API для Admin UI: CRUD endpoints, фильтрованный лог доставок, ручной ретрай.
День 3 — фронтенд Admin UI (таблица endpoints, лог доставок с drill-down до тела запроса/ответа), тест ping, мониторинг failed/pending counts.
Этапы разработки дашборда
| Этап | Длительность | Результат |
|---|---|---|
| Аналитика и проектирование | 2–3 дня | ТЗ на схему БД и API |
| Backend: доставка и API | 5–7 дней | DeliveryService, Jobs, REST API |
| Frontend: Admin UI | 4–6 дней | Интерфейс управления и мониторинга |
| Тестирование и деплой | 2–3 дня | Staging + production, обучение команды |
Что входит в работу
- Схема БД с миграциями и индексами
- Сервис доставки с HMAC-подписью и exponential backoff
- REST API для управления подписками и просмотра логов
- Административный UI (React/Vue) с фильтрацией и drill-down
- Мониторинг и алерты при превышении порога ошибок
- Документация API (Swagger/OpenAPI)
- Деплой на ваш сервер или облако
Результаты и гарантии
Наш опыт: более 5 лет в интеграции микросервисов, 50+ успешных проектов с вебхуками. Гарантируем стабильную работу под высокой нагрузкой. Сокращает время на поиск ошибок на 40% и экономит до 30% бюджета на поддержку.
Если вы хотите получить полный контроль над вашими вебхуками — закажите разработку дашборда. Свяжитесь с нами, чтобы обсудить ваш проект. Получите консультацию и расчет сроков.







