Что такое webhook простыми словами и как не терять события
Следующий шаг
Открой бота или продолжай маршрут внутри раздела.
Статья -> план в ИИ
Отправь ссылку на эту статью в любой ИИ и получи план внедрения под свой проект.
Прочитай эту статью: https://vibecode.morecil.ru/ru/integratsii-i-api/what-is-webhook/
Работай в контексте моего текущего проекта.
Сделай план внедрения под мой стек:
1) что изменить
2) в каких файлах
3) риски и типичные ошибки
4) как проверить, что всё работает
Если есть варианты, дай "быстрый" и "production-ready". Как использовать
- Скопируй этот промпт и отправь в чат с ИИ.
- Прикрепи проект или открой папку репозитория в ИИ-инструменте.
- Попроси изменения по файлам, риски и короткий чеклист проверки.
Когда ты только начинаешь работать с интеграциями, webhook часто кажется чем-то сложным и ненадёжным. События то приходят, то дублируются, то вдруг бесследно исчезают, и ты тратишь часы на поиски причины.
Эта статья написана специально для тех, кто хочет разобраться с нуля: максимально просто, но с рабочей архитектурой, которую можно внедрить уже сегодня.
## Коротко
- Webhook — это когда внешний сервис сам отправляет тебе данные на указанный URL сразу после события.
- Он заменяет постоянный опрос API (polling) и даёт почти мгновенную реакцию.
- Большинство потерь событий происходит не из-за самого webhook, а из-за неправильной обработки на твоей стороне.
- Надёжная схема выглядит так: проверка подписи → сохранение в БД → мгновенный 200 OK → обработка в очереди.
- При правильной идемпотентности, ретраях и мониторинге потери событий практически исчезают.
## Пример «на пальцах»
Простыми словами
Представь, что ты заказал доставку суши. Вместо того чтобы каждые пять минут звонить в ресторан и спрашивать «ну как там, уже выехали?», курьер сам звонит в домофон, когда приезжает к твоему подъезду.
Webhook работает точно так же. Ты один раз даёшь сервису свой адрес (URL), и когда происходит нужное событие, он сам «звонит в дверь» и передаёт информацию.
Как это выглядит в реальном проекте
- Платёжная система (Stripe, ЮKassa, Tinkoff) сообщает: «платёж прошёл».
- GitHub пишет: «новый коммит запушен».
- Telegram-бот получает сообщение от пользователя.
- CRM-система сообщает: «клиент оставил заявку» или «сделка перешла в другой статус».
Всё это приходит в виде обычного HTTP POST-запроса с JSON-данными.
## Словарь терминов (без воды)
- Webhook — уведомление от внешнего сервиса на твой сервер.
- Endpoint — конкретный URL, на который приходят webhook'и.
- Payload — тело запроса, обычно JSON с информацией о событии.
- Signature — криптографическая подпись запроса, чтобы убедиться, что он действительно от нужного сервиса.
- Idempotency (идемпотентность) — свойство системы, при котором повторная обработка одного и того же события не приводит к дублированию действий.
- Retry — автоматический повтор отправки события, если ты не подтвердил получение.
- Queue (очередь) — механизм для фоновой обработки тяжёлых задач.
- DLQ (Dead Letter Queue) — «кладбище» событий, которые не удалось обработать после всех попыток.
- Replay — возможность вручную повторить обработку конкретного события.
## Когда webhook нужен, а когда можно обойтись без него
Webhook действительно необходим, если:
- Тебе важна скорость реакции (почти в реальном времени).
- События происходят неравномерно и редко.
- Постоянный polling будет слишком дорогим по лимитам API или трафику.
- Ты хочешь строить современную событийно-ориентированную архитектуру.
Можно обойтись без webhook, если:
- Допустима задержка в несколько минут.
- Проект совсем маленький и событий мало.
- Сервис не поддерживает webhook (тогда polling — единственный вариант).
## Почему события теряются: 7 реальных причин
- Endpoint недоступен во время деплоя, падения сервера или проблем с SSL.
- Долгая обработка прямо в контроллере — внешний сервис получает таймаут и считает событие не доставленным.
- Отсутствие проверки подписи — валидные события теряются среди мусора.
- Нет идемпотентности — при ретраях происходят двойные действия (двойной платёж, двойная выдача доступа).
- Отсутствие очереди — при пиковой нагрузке сервер не успевает обработать все запросы.
- Нет хранения сырых событий — когда что-то пошло не так, ты даже не знаешь, что событие приходило.
- Отсутствие DLQ и replay — упавшие события просто исчезают без возможности восстановления.
## Рекомендуемая базовая архитектура
Правильная обработка webhook строится по чёткому принципу:
- Принять POST-запрос на endpoint.
- Проверить подпись.
- Сохранить сырое событие в базу данных (с уникальностью по event_id).
- Моментально вернуть
200 OK. - Отправить задачу в очередь.
- В фоне (воркер) выполнить бизнес-логику.
- Вести статусы и логировать всё.
Главное правило: HTTP-эндпоинт должен работать максимально быстро. Вся тяжёлая работа — только после подтверждения получения.
## Реализация по уровням: от простого к надёжному
Уровень 1 — База
Что делаем:
- Создаём endpoint для webhook.
- Проверяем подпись.
- Сохраняем полное событие в таблицу
webhook_events. - Возвращаем
200 OKкак можно быстрее.
Что это даёт:
- У тебя есть полный лог всех пришедших событий.
- Значительно меньше потерь из-за таймаутов.
- Возможность расследовать любые проблемы в будущем.
Уровень 2 — Рабочий минимум
Что добавляем:
- Очередь задач (Laravel Horizon + Redis).
- Job для фоновой обработки.
- Статусы события (
received,processing,succeeded,failed). - Автоматические ретраи (5–10 попыток с exponential backoff).
Что это даёт:
- Система выдерживает пики нагрузки.
- Ошибки не убивают обработку других событий.
- Повторные попытки происходят автоматически.
Уровень 3 — Надёжный продакшен
Что добавляем:
- Dead Letter Queue для проблемных событий.
- Функцию Replay (повтор обработки из админки).
- Полноценный мониторинг и алерты.
- Продвинутую идемпотентность на уровне бизнес-сущностей.
Что это даёт:
- Почти нулевые потери.
- Быстрое восстановление после сбоев.
- Полная наблюдаемость системы.
## Конкретный пример: Laravel 12
Вот минимальная, но уже надёжная реализация:
// routes/web.php
Route::post('/webhooks/stripe', [WebhookController::class, 'handleStripe']);
// app/Http/Controllers/WebhookController.php
public function handleStripe(Request $request)
{
$payload = $request->getContent();
$signature = $request->header('Stripe-Signature');
if (!$this->verifySignature($payload, $signature)) {
return response('Invalid signature', 401);
}
$event = json_decode($payload, true);
$eventId = $event['id'] ?? null;
if (!$eventId) {
return response('Missing event id', 400);
}
// Сохраняем сырое событие (идемпотентно)
WebhookEvent::updateOrCreate(
['provider' => 'stripe', 'event_id' => $eventId],
[
'type' => $event['type'],
'payload' => $event,
'status' => 'received'
]
);
// Отправляем в очередь
ProcessWebhookJob::dispatch($eventId, 'stripe');
return response('OK', 200);
}
// app/Jobs/ProcessWebhookJob.php
public function handle()
{
$webhookEvent = WebhookEvent::where('event_id', $this->eventId)->firstOrFail();
try {
$webhookEvent->markAsProcessing();
// Здесь твоя бизнес-логика
match($webhookEvent->type) {
'invoice.paid' => $this->handleSuccessfulPayment($webhookEvent->payload),
// другие типы событий...
};
$webhookEvent->markAsSucceeded();
} catch (\Exception $e) {
$webhookEvent->markAsFailed($e->getMessage());
throw $e; // для ретраев Laravel
}
}
## SQL: таблица, которая спасает от дублей
Schema::create('webhook_events', function (Blueprint $table) {
$table->id();
$table->string('provider');
$table->string('event_id')->unique();
$table->string('type');
$table->json('payload');
$table->string('status')->default('received');
$table->text('error_message')->nullable();
$table->timestamp('received_at')->useCurrent();
$table->timestamp('processed_at')->nullable();
$table->timestamps();
});
Почему это важно:
- Один и тот же webhook может прийти 2–5 раз.
- Без уникального индекса ты легко сделаешь двойной платёж, двойное письмо или двойную выдачу доступа.
## Какие инструменты реально использовать
- Laravel Horizon + Redis — для очередей и удобного дашборда.
- spatie/laravel-webhook-client — готовое решение (можно взять за основу).
- ngrok или Cloudflare Tunnel — для тестирования локально.
- Laravel Telescope или Pulse — для отладки.
- Sentry / Bugsnag — отслеживание ошибок в джобах.
Если проект маленький — достаточно Redis + один воркер + простые логи.
## Что мониторить обязательно
Минимум метрик:
webhook_received_totalwebhook_failed_totalwebhook_processing_latency_msqueue_depthdlq_count
Минимум алертов:
- рост
failed_total - очередь растёт слишком быстро
- endpoint даёт 5xx выше порога
## Типичные ошибки
- Делают всю логику прямо в endpoint
Исправление: endpoint только принимает и подтверждает, обработка — в фоне. - Игнорируют подпись
Исправление: без подписи нельзя доверять событию. - Нет idempotency
Исправление:provider + event_idдолжен быть уникальным. - Нет retry и DLQ
Исправление: повторы + отдельная очередь для неуспешных. - Нет replay
Исправление: сделай команду или кнопку «повторить обработку».
## Чеклист внедрения
Endpoint для webhook
Проверка подписи
Таблица
webhook_events+ unique индексБыстрый
200после записиОчередь и воркер
Статусы обработки
Retry policy
Базовые логи по
event_idDLQ
Replay
Метрики и алерты
Runbook «что делать при росте failed»
## FAQ
Webhook это сложно?
Нет. Сложно не принять webhook, а сделать обработку надежной.
Почему дубли — это нормально?
Провайдеры делают retry, если не уверены, что ты принял событие.
Можно ли без очереди?
Можно на старте, но при росте нагрузки ты начнешь терять события.
Когда возвращать 200?
После безопасной фиксации события, не после полной обработки.
Polling лучше webhook?
Обычно нет. Polling тяжелее, медленнее и дороже по запросам.
## Итог
Webhook — это простой механизм, который становится надёжным только с правильной обработкой.
Формула для практики:проверка подписи → идемпотентная запись → быстрый 200 → очередь → воркер → мониторинг.