Реалізація Webhook-системи для інтеграцій сайту

Наша компанія займається розробкою, підтримкою та обслуговуванням сайтів будь-якої складності. Від простих односторінкових сайтів до масштабних кластерних систем, побудованих на мікро сервісах. Досвід розробників підтверджено сертифікатами від вендорів.
Розробка та обслуговування будь-яких видів сайтів:
Інформаційні сайти або веб-програми
Сайти візитки, landing page, корпоративні сайти, онлайн каталоги, квіз, промо-сайти, блоги, ресурси новин, інформаційні портали, форуми, агрегатори
Сайти або веб-програми електронної комерції
Інтернет-магазини, B2B-портали, маркетплейси, онлайн-обмінники, кешбек-сайти, біржі, дропшиппінг-платформи, парсери товарів
Веб-програми для управління бізнес-процесами
CRM-системи, ERP-системи, корпоративні портали, системи управління виробництвом, парсери інформації
Сайти або веб-програми електронних послуг
Дошки оголошень, онлайн-школи, онлайн-кінотеатри, конструктори сайтів, портали надання електронних послуг, відеохостинги, тематичні портали

Це лише деякі з технічних типів сайтів, з якими ми працюємо, і кожен із них може мати свої специфічні особливості та функціональність, а також бути адаптованим під конкретні потреби та цілі клієнта.

Пропоновані послуги
Показано 1 з 1 послугУсі 2065 послуг
Реалізація Webhook-системи для інтеграцій сайту
Середня
~3-5 робочих днів
Часті питання
Наші компетенції:
Етапи розробки
Останні роботи
  • image_website-b2b-advance_0.png
    Розробка сайту компанії B2B ADVANCE
    1262
  • image_web-applications_feedme_466_0.webp
    Розробка веб-додатків для компанії FEEDME
    1171
  • image_websites_belfingroup_462_0.webp
    Розробка веб-сайту для компанії БЕЛФІНГРУП
    874
  • image_ecommerce_furnoro_435_0.webp
    Розробка інтернет магазину для компанії FURNORO
    1094
  • image_crm_enviok_479_0.webp
    Розробка веб-додатків для компанії Enviok
    831
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Розробка веб-сайту для компанії ФІКСПЕР
    851

Реалізація Webhook-системи для інтеграцій сайту

Webhook — це HTTP-колбек: зовнішній сервіс відправляє POST-запит на зазначений URL у момент наступлення події. Принципово відрізняється від polling-підходу: не сайт періодично запитує «є ли нові дані?», а джерело сам сповіщає при зміні. Webhook-система на сайті може працювати в двох ролях: приймати вхідні події від зовнішніх сервісів та відправляти вихідні події для сторонніх підписників.

Вхідні webhook: приймання та обробка

Базова архітектура для приймання подій від зовнішніх систем (платіжні шлюзи, CRM, маркетплейси):

// routes/api.php
Route::post('/webhooks/{provider}', WebhookController::class);
class WebhookController extends Controller
{
    public function __invoke(Request $request, string $provider): JsonResponse
    {
        // 1. Логуємо сирий запит до будь-якої обробки
        WebhookLog::create([
            'provider'   => $provider,
            'headers'    => $request->headers->all(),
            'payload'    => $request->getContent(),
            'ip'         => $request->ip(),
            'received_at' => now(),
        ]);

        // 2. Верифікуємо підпис
        $handler = WebhookHandlerFactory::make($provider);
        if (!$handler->verify($request)) {
            return response()->json(['error' => 'Invalid signature'], 401);
        }

        // 3. Ставимо в очередь — не обробляємо синхронно
        ProcessWebhookJob::dispatch($provider, $request->all())
            ->onQueue('webhooks');

        // 4. Відповідаємо швидко — зовнішній сервіс чекає 3–10 секунд
        return response()->json(['ok' => true]);
    }
}

Критичний момент: відповідь повинна вийти за 3–10 секунд (ліміт у більшості провайдерів). Будь-яка важка логіка — в очереді.

Верифікація підписів

Кожен провайдер підписує payload по-своєму. Кілька прикладів:

Stripe / HMAC-SHA256:

$secret    = config('services.stripe.webhook_secret');
$sigHeader = $request->header('Stripe-Signature');
$payload   = $request->getContent();

// Stripe використовує timestamp + HMAC
[$t, $v1] = $this->parseStripeSignature($sigHeader);
$signed    = hash_hmac('sha256', "{$t}.{$payload}", $secret);
if (!hash_equals($signed, $v1)) {
    throw new InvalidSignatureException();
}

GitHub / SHA-256 з префіксом:

$secret    = config('services.github.webhook_secret');
$signature = $request->header('X-Hub-Signature-256');
$expected  = 'sha256=' . hash_hmac('sha256', $request->getContent(), $secret);

if (!hash_equals($expected, $signature)) {
    abort(401);
}

Простий токен у заголовку (багато CRM):

$token = $request->header('X-Webhook-Token');
if (!hash_equals(config('services.crm.webhook_token'), $token)) {
    abort(403);
}

Завжди використовуйте hash_equals замість === — захист від timing-атак.

Ідемпотентність та дедупликація

Зовнішні сервіси можуть прислати одну подію кілька разів: при тайм-ауті, після перезапуску, за політикою retry. Потрібна дедупликація:

class ProcessWebhookJob implements ShouldQueue
{
    use Dispatchable, InteractsWithQueue;

    public string $uniqueFor = 3600; // унікальність протягом години

    public function handle(): void
    {
        $eventId = $this->payload['id'] ?? $this->payload['event_id'] ?? null;

        if ($eventId && WebhookEvent::where('external_id', $eventId)->exists()) {
            Log::info("Webhook duplicate skipped: {$eventId}");
            return;
        }

        // Зберігаємо event_id до обробки
        WebhookEvent::create(['external_id' => $eventId, 'processed_at' => now()]);

        // Основна логіка
        $this->process();
    }
}

Вихідні webhook: відправка подій підписникам

Якщо сайт сам є джерелом подій (наприклад, інтернет-магазин сповіщає партнерів про зміни замовлень), потрібна система управління підписками:

// Таблиця підписників
Schema::create('webhook_subscriptions', function (Blueprint $table) {
    $table->id();
    $table->string('url');
    $table->string('event');       // 'order.created', 'order.status_changed'
    $table->string('secret');      // для HMAC-підпису
    $table->boolean('active')->default(true);
    $table->integer('failures')->default(0);
    $table->timestamp('last_error_at')->nullable();
    $table->timestamps();
});

Відправка події всім підписникам:

class WebhookDispatcher
{
    public function dispatch(string $event, array $payload): void
    {
        $subscriptions = WebhookSubscription::active()
            ->where('event', $event)
            ->get();

        foreach ($subscriptions as $subscription) {
            SendWebhookJob::dispatch($subscription, $payload)
                ->onQueue('webhooks-outgoing');
        }
    }
}
class SendWebhookJob implements ShouldQueue
{
    public int $tries = 5;
    public array $backoff = [60, 300, 900, 3600, 10800]; // експоненціальний backoff

    public function handle(): void
    {
        $body      = json_encode($this->payload);
        $timestamp = time();
        $signature = hash_hmac('sha256', "{$timestamp}.{$body}", $this->subscription->secret);

        $response = Http::withHeaders([
            'Content-Type'       => 'application/json',
            'X-Webhook-Event'    => $this->payload['event'],
            'X-Webhook-Timestamp'=> $timestamp,
            'X-Webhook-Signature'=> "sha256={$signature}",
        ])
        ->timeout(10)
        ->post($this->subscription->url, $this->payload);

        if (!$response->successful()) {
            $this->subscription->increment('failures');
            if ($this->subscription->failures >= 10) {
                $this->subscription->update(['active' => false]);
                // повідомити власника підписки
            }
            $this->fail("HTTP {$response->status()}");
        } else {
            $this->subscription->update(['failures' => 0]);
        }
    }
}

Дашборд мониторингу

Для отладки потрібен інтерфейс для перегляду вхідних/вихідних подій: статус, час, payload, відповідь. Мінімальний варіант — таблиця webhook_logs + простий CRUD-контролер в админці. Продвинутий — інтеграція з Laravel Telescope або окрема сторінка зі статистикою по провайдерам.

Retry та dead letter queue

Необроблені після всіх спроб завдання повинні потрапляти в окрему очередь для ручного розбора:

// queue.php
'connections' => [
    'redis' => [
        'driver' => 'redis',
        'queue'  => 'webhooks',
        'retry_after' => 90,
    ],
],

// У job
public function failed(Throwable $e): void
{
    WebhookFailure::create([
        'provider' => $this->provider,
        'payload'  => $this->payload,
        'error'    => $e->getMessage(),
    ]);
    // алерт в Slack/Telegram
}

Терміни

Базова система приймання webhook від одного провайдера з верифікацією та очередю: 1 робочий день. Повнофункціональна платформа з управлінням підписками, дашбордом, retry-логікою та мониторингом: 3–5 робочих днів в залежності від кількості провайдерів та вимог до UI.