Inquir Compute logoInquir Compute
Паттерн · Inquir Compute

Serverless-обработчик вебхуков с повторами, логами и фоновыми задачами

Паттерн: проверьте HMAC-подпись на шлюзе, ответьте 200 за 100 мс, передайте тяжёлую работу в пайплайн. Провайдер не делает повторную попытку; дублей не возникает.

Last updated: 2026-04-20

  • HMAC signature verification on raw request body before any parsing
  • Fast 200 ACK inside Stripe, GitHub, Slack, and Shopify timeout windows
  • Async handoff to pipelines or jobs for slow downstream work
  • Idempotency keys and execution traces for duplicate deliveries

Direct answer

Serverless-обработчик вебхуков с повторами, логами и фоновыми задачами. Функция проверяет HMAC и сохраняет ключ идемпотентности, возвращает 200, вызывает `global.durable.startNew()`. Оркестрация продолжает работу вне HTTP-окна.

When it fits

  • Stripe, GitHub, Slack или любой другой провайдер доставляет вебхуки
  • Обработка занимает больше 2–5 секунд

Tradeoffs

  • Синхронная обработка внутри HTTP-запроса — таймаут гарантирован при росте нагрузки или медленном downstream.
  • Без ключа идемпотентности повторные доставки провайдера создают дубли в базе или дублируют вызовы API.

Почему вебхуки теряются или дублируются

Если обработчик отвечает 200 только после тяжёлой работы, тайм-аут провайдера вызывает повторную попытку — задача выполняется дважды.

Без проверки подписи любой запрос со знакомым URL может сымитировать вебхук и вызвать побочные эффекты.

Где ломаются лёгкие подходы

Синхронная обработка внутри HTTP-запроса — таймаут гарантирован при росте нагрузки или медленном downstream.

Без ключа идемпотентности повторные доставки провайдера создают дубли в базе или дублируют вызовы API.

Как Inquir решает проблему

Функция проверяет HMAC и сохраняет ключ идемпотентности, возвращает 200, вызывает `global.durable.startNew()`. Оркестрация продолжает работу вне HTTP-окна.

Один шлюз принимает вебхуки Stripe, GitHub, Slack с раздельными маршрутами и API-ключами — без дополнительного nginx.

Что нужно надёжному обработчику вебхуков

Проверка подписи

HMAC SHA-256 timing-safe сравнение с секретом провайдера через переменные окружения функции.

Быстрый ACK

Ответить 200 до тяжёлой работы — иначе таймаут провайдера вызовет повтор.

Ключ идемпотентности

Проверить ID доставки перед записью; повторная доставка не создаёт дубль.

Запуск оркестрации

`global.durable.startNew()` запускает тяжёлую работу асинхронно с теми же логами и секретами.

Как построить обработчик вебхука

Verify on the raw body, ACK before the timeout, apply writes idempotently.

1

Проверить подпись

Сравните HMAC из заголовка с вычисленным значением — до любой другой работы.

2

Ответить 200 быстро

Запишите ключ идемпотентности и вызовите `global.durable.startNew()` до возврата ответа.

3

Обработать событие в пайплайне

Шаги пайплайна выполняют тяжёлую работу: API-вызовы, запись в БД, нотификации.

Stripe and GitHub webhook handlers

One function per provider. body comes in as a string — never parse before verifying. Use timing-safe comparison to resist timing attacks on HMAC checks.

webhooks/stripe.mjs
export async function handler(event) {
  const rawBody = event.body ?? '';
  // Stripe signs the raw bytes — never parse before verifying
  const sig = event.headers['stripe-signature'] ?? '';
  if (!stripe.webhooks.verifySignature(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET)) {
    return { statusCode: 400, body: 'invalid signature' };
  }
  const evt = JSON.parse(rawBody);
  // Block duplicate delivery before any side effects
  const isNew = await db.upsertWebhookEvent(evt.id, evt.type);
  if (!isNew) return { statusCode: 200, body: 'duplicate' };
  // Return fast; continue in pipeline
  await global.durable.startNew('stripe-fulfillment', undefined, { eventId: evt.id, type: evt.type, data: evt.data.object });
  return { statusCode: 200, body: 'accepted' };
}
webhooks/github.mjs
import { createHmac, timingSafeEqual } from 'node:crypto';

export async function handler(event) {
  const body = event.body ?? '';
  const sigHeader = (event.headers['x-hub-signature-256'] ?? '').replace('sha256=', '');
  const expected = createHmac('sha256', process.env.GITHUB_WEBHOOK_SECRET).update(body).digest('hex');
  if (!timingSafeEqual(Buffer.from(sigHeader, 'hex'), Buffer.from(expected, 'hex'))) {
    return { statusCode: 401, body: 'invalid signature' };
  }
  const eventType = event.headers['x-github-event'];
  const payload = JSON.parse(body);
  if (eventType === 'push') {
    await global.durable.startNew('index-repo', undefined, { repo: payload.repository.full_name, sha: payload.after });
  }
  return { statusCode: 200, body: 'accepted' };
}

Когда нужен этот паттерн

Когда это уместно

  • Stripe, GitHub, Slack или любой другой провайдер доставляет вебхуки
  • Обработка занимает больше 2–5 секунд

Когда лучше не трогать

  • Лёгкий вебхук без side-effects, который отвечает за 100 мс — пайплайн избыточен

Вопросы и ответы

Как проверить подпись Stripe?

Raw body с `transfer-encoding: identity`, HMAC SHA-256 с ключом из переменной окружения, timing-safe сравнение — полный код в примере страницы.

Что делать при дубле от провайдера?

Записать ID доставки в базу с `INSERT … ON CONFLICT DO NOTHING` до начала основной работы.

What about replay attacks?

Combine HMAC verification, provider-supplied timestamps (check within ±5 minutes), and idempotency keys so late replays are rejected and duplicate deliveries are no-ops.

Can I test locally?

Use the Stripe CLI `stripe listen --forward-to` or similar tools to forward real webhook events to a local handler. Keep the same event.body string contract as the gateway delivers in production.

Inquir Compute logoInquir Compute

Самый простой способ запускать AI-агентов и backend-джобы без инфраструктуры.

Связаться info@inquir.org

© 2025 Inquir Compute. Все права защищены.