Розробка 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% бюджету на підтримку.
Якщо ви хочете отримати повний контроль над вашими вебхуками — замовте розробку дашборду. Зв'яжіться з нами, щоб обговорити ваш проект. Отримайте консультацію та розрахунок термінів.







