Реализация 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-система на сайте может работать в двух ролях: принимать входящие события от внешних сервисов и отправлять исходящие события для сторонних подписчиков.

Входящие webhooks: приём и обработка

Базовая архитектура для приёма событий от внешних систем (платёжные шлюзы, 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. Отвечаем быстро — внешний сервис ждёт 200 OK
        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 вместо === — защита от тайминг-атак.

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

Внешние сервисы могут прислать одно событие несколько раз: при таймауте, после перезапуска, по политике 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();
    }
}

Исходящие webhooks: отправка событий подписчикам

Если сайт сам является источником событий (например, интернет-магазин уведомляет партнёров об изменении заказов), нужна система управления подписками:

// Таблица подписчиков
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
}

Сроки

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