Skip to main content

Webhooks (Асинхронные вебхуки)

Архитектурный паттерн асинхронного межсервисного взаимодействия (Event-driven Push), при котором провайдер события отправляет HTTP POST-запрос с данными на зарегистрированный URL потребителя при наступлении системного события.

1. Обзор концепции и системная проблема

Традиционная интеграция систем через постоянное опрос (Polling / Short Polling) страдает от двух взаимоисключающих проблем:

  1. Высокая задержка реакции (Latency): Если клиент проверяет изменения каждые 60 секунд, данные доставляются в среднем с задержкой в 30 секунд.
  2. Колоссальное мар wasting вычислительных ресурсов: 99% HTTP-запросов возвращают пустой ответ 304 Not Modified или [], перегружая базу данных и сетевой стек.

Webhooks (Обратные вызовы через HTTP) меняют парадигму с Pull на Push. Вместо того чтобы запрашивать: "Есть новые данные?", потребитель регистрирует свой URL (Webhook Listener). Как только в системе провайдера происходит событие (успешная транзакция Stripe, новый коммит в GitHub, входящее сообщение Telegram), провайдер отправляет HTTP POST-запрос с JSON payload непосредственно на сервер подписчика.

Традиционный Polling (Мар wasting ресурсов):
Клиент  ---> GET /events?since=... ---> Сервер (Пусто: [])
Клиент  ---> GET /events?since=... ---> Сервер (Пусто: [])
Клиент  ---> GET /events?since=... ---> Сервер (Новое событие!)

Event-Driven Webhook (Мгновенный Push):
Провайдер событий (Stripe / GitHub / Telegram)
       |
       |  Событие: charge.succeeded (Event Payload + HMAC подпись)
       |  HTTP POST https://api.mysite.com/webhooks/stripe
       v
Потребитель (Валидация подписи -> 200 OK -> Фоновая очередь RabbitMQ/Redis)

2. Архитектурная таксономия и ментальная модель

Жизненный цикл вебхука опирается на три архитектурные опоры:

  1. Криптографическая аутентификация (HMAC Signature):
    • Провайдер генерирует криптографический хеш от тела запроса (raw body) с помощью общего секретного ключа (Webhook Secret), добавляя его в заголовок (например, Stripe-Signature или X-Hub-Signature-256).
    • Потребитель вычисляет такой же хеш и валидирует его, исключая риск подделки запроса злоумышленником.
  2. Идемпотентность и дедупликация (Idempotency Engine):
    • Каждое событие имеет глобальный уникальный ID (evt_xxxxxxxxxx).
    • Потребитель атомарно проверяет наличие ID в кеше/БД (например, через INSERT ... ON CONFLICT DO NOTHING или Redis SET key NX EX 86400).
  3. Политика гарантированной доставки и повторов (Retry & Dead-Letter Queue):
    • В случае недоступности сервера потребителя провайдер повторяет запросы по экспоненциальной шкале (Exponential Backoff с джитером): через 5 минут, 30 минут, 2 часа, 12 часов.
    • События, которые не удалось обработать после максимального количества попыток, направляются в Dead-Letter Queue (DLQ) для ручного анализа инженерами.

3. Технический пайплайн и внутренняя механика

Производственный обработчик с верификацией HMAC (Node.js / Express / TypeScript)

[!IMPORTANT] Для проверки подписи необходимо получать необработанное тело запроса (raw buffer), а не распарсенный объект req.body, иначе хеш не сойдется из-за различий пробелов и сортировки ключей JSON.

import express, { Request, Response } from "express";
import crypto from "crypto";

const app = express();

// Получение raw buffer для эндпоинтов вебхуков
app.post(
  "/api/v1/stripe-webhook",
  express.raw({ type: "application/json" }),
  async (req: Request, res: Response) => {
    const signature = req.headers["stripe-signature"] as string;
    const secret = process.env.STRIPE_WEBHOOK_SECRET!;

    if (!signature) {
      return res.status(400).send("Missing signature header");
    }

    // 1. Криптографическая валидация подписи через HMAC SHA-256
    const computedHmac = crypto
      .createHmac("sha256", secret)
      .update(req.body)
      .digest("hex");

    // Безопасное сравнение за фиксированное время
    const isValid = crypto.timingSafeEqual(
      Buffer.from(signature),
      Buffer.from(computedHmac)
    );

    if (!isValid) {
      return res.status(401).send("Invalid signature");
    }

    const payload = JSON.parse(req.body.toString());
    const eventId = payload.id;

    // 2. Идемпотентная проверка дедупликации
    const isNew = await redis.set(`webhook:processed:${eventId}`, "1", "NX", "EX", 86400);
    if (!isNew) {
      // Событие уже обработано — возвращаем 200 OK без повторного выполнения
      return res.status(200).json({ status: "already_processed" });
    }

    // 3. Отправка тяжелой работы в очередь BullMQ
    await eventQueue.add("process_payment", payload);

    // 4. Мгновенное подтверждение получения
    return res.status(200).json({ received: true });
  }
);

4. Практические инженерные сценарии в продакшене

01. Платежный шлюз для AI SaaS (Stripe / Paddle / LiqPay)

Пользователь оплачивает подписку на Pro-план. Платежная система генерирует событие invoice.payment_succeeded. Вебхук мгновенно обновляет статус пользователя в базе данных, сбрасывает лимиты токенов в Redis и отправляет приветственное письмо, позволяя пользователю продолжить работу без перезагрузки страницы.

02. Триггер автономного CI/CD пайплайна (GitHub Webhooks)

Инженер выполняет git push в ветку main. GitHub отправляет payload с хешем коммита на собственный сервер Coolify или ArgoCD. Сервер проверяет секрет, клонирует измененный код, запускает unit-тесты в изолированном Docker-контейнере и обновляет production-сервис по стратегии Blue/Green.

03. Асинхронный сбор результатов инференса тяжелых AI-моделей

При вызове моделей генерации видео (Runway, Kling) или тренировки LoRA запрос длится несколько минут. Вместо удержания открытого HTTP-соединения клиент передает webhook_url. Как только кластер GPU завершает генерацию, он отправляет вебхук с финальной S3-ссылкой на сгенерированное медиа.


5. Подводные камни, типовые ошибки и безопасность

  1. Парсинг JSON перед верификацией подписи: Если перед проверкой подписи отработал стандартный мидлвар express.json(), сериализатор может изменить порядок полей, экранирование Unicode-символов или форматирование чисел, что приведет к несовпадению контрольной суммы HMAC и отказу Invalid Signature.
  2. Синхронное блокирование выполнения (Webhook Deadlock): Выполнение тяжелых транзакций или длительных HTTP-запросов к внешним API внутри обработчика вебхука гарантированно приводит к таймауту на стороне провайдера. Возвращайте 200 OK мгновенно после валидации подписи и сохранения в очередь.
  3. Отсутствие валидации временных меток (Replay Attacks): Злоумышленник, перехвативший легитимный подписанный вебхук-запрос, может отправить его повторно через час. Провайдеры добавляют в подпись временную метку (timestamp). Проверяйте, чтобы разница между временем запроса и системными часами сервера не превышала допустимое окно (например, 5 минут).
/ Частые вопросыSchema.org FAQPage

FAQ: Webhooks (Асинхронные вебхуки)

Классическое сравнение строк через операторы == или === прерывается на первом несовпадающем символе. Злоумышленник может измерять задержку ответа сервера с наноточностью (Timing Attack) и побайтово подобрать валидный хеш подписи. Функция timingSafeEqual выполняет сравнение за фиксированное время, гарантируя криптографическую стойкость.
/ Внутренняя перелинковка
Все термины
VPS и DevOps

Telegram Bot API (API Ботов Telegram)

Официальный HTTP-интерфейс платформы Telegram, позволяющий создавать автономных чат-ботов, AI-ассистентов, интерфейсы Telegram Mini Apps (TMA) и каналы интерактивного уведомления.

Читать термин
VPS и DevOps

Крон-планировщики (Cron Schedulers & Systemd Timers)

Системные демоны (Linux cron, systemd timers) и распределенные очереди (BullMQ, Temporal), обеспечивающие гарантированный запуск периодических инженерных задач, бэкапов, синхронизации данных и AI-агентов по расписанию.

Читать термин
VPS и DevOps

Rate Limiting (Ограничение частоты запросов и защита API)

Системный механизм контроля интенсивности входящего и исходящего трафика (Token Bucket, Sliding Window) для защиты бэкенда от исчерпания ресурсов, брутфорса, Layer 7 DDoS и финансового овердрафта на AI-эндпоинтах.

Читать термин
VPS и DevOps

Обратный прокси (Nginx, Caddy, Traefik)

Промежуточный серверный архитектурный слой, который принимает внешний интернет-трафик (порты 80/443), выполняет терминацию SSL/TLS, сжатие (Brotli/Gzip), кэширование статических файлов и безопасно маршрутизирует запросы к внутренним приложениям.

Читать термин