Сценарий

Serverless-обработчики вебхуков для API и провайдеров

Stripe, GitHub, Slack, Shopify: проверяйте подпись по сырому телу запроса, отвечайте до таймаута провайдера, выносите медленную работу в асинхронную обработку, закладывайте идемпотентность на случай повторных доставок и сохраняйте трассы, когда ретраи накладываются друг на друга.

Обновлено: 2026-06-28

Суть ответа

Serverless-обработчики вебхуков для API и провайдеров. Отдельные функции под каждого провайдера снижают случайное связывание между обработчиками вебхуков.

Когда подходит

  • Интеграции SaaS
  • Платёжные провайдеры
  • События Git-хостинга

На что обратить внимание

  • Пропуск проверки подписи «на время» оставляет эндпоинт открытым для поддельных payload — задолго после того, как об упрощении забыли.
  • Синхронные записи в базу до ответа 200 OK провоцируют повторную доставку от провайдера — и дубли записей — при любом всплеске задержки на следующем шаге.

Типичные сбои вебхуков

Повторные доставки и медленные вызовы следующих систем заканчиваются таймаутами, если с самого начала не заложить идемпотентность и асинхронную передачу работы.

Тяжёлые записи до ответа 2xx означают повторную доставку — и двойное списание или двойную запись, если не каждый путь идемпотентен.

Один перегруженный эндпоинт тянет вниз не связанный с ним пользовательский трафик.

Типичные упрощения

Пропуск проверки подписи «на время» оставляет эндпоинт открытым для поддельных payload — задолго после того, как об упрощении забыли.

Синхронные записи в базу до ответа 200 OK провоцируют повторную доставку от провайдера — и дубли записей — при любом всплеске задержки на следующем шаге.

Вебхук: проверить, быстро ответить, продолжить асинхронно

Отдельные функции под каждого провайдера снижают случайное связывание между обработчиками вебхуков.

Пайплайны продолжают работу после быстрого HTTP-ответа, поэтому таймауты провайдера никогда не блокируют идемпотентные записи.

Инструменты шлюза для вебхуков: авторизация, трассы, тенанты

Авторизация на маршруте

API-ключи или свои схемы авторизации на каждый входящий маршрут — до запуска кода обработчика.

Трассировка

Трассы показывают, на каком этапе случился сбой — проверка подписи, постановка в очередь или запись, — и ретраи отлаживаются по фактам, а не по догадкам.

Маршрутизация тенантов

Аккуратно разводите хосты или префиксы путей, когда у каждого клиента свой URL вебхука.

Как обрабатывать вебхуки на Inquir Compute

Проверка подписи, ответ в пределах таймаута провайдера, идемпотентные записи.

1

Проверить сырой запрос и дедуплицировать по ID события

Отклоняйте неверные подписи и блокируйте дубликаты до любых изменений состояния.

2

Вернуть успех в окне таймаута

Подтверждайте получение быстро, чтобы провайдер не делал лишних повторных доставок.

3

Поставить тяжёлую работу в очередь и писать идемпотентно

Продолжайте асинхронно и держите побочные эффекты безопасными для повторного проигрывания.

Паттерны реализации под конкретного провайдера

У реальных провайдеров свои заголовки подписи и ограничения по таймауту. Заводите отдельную функцию на провайдера — логика проверки остаётся маленькой и удобной для ревью.

webhooks/stripe.mjs
import Stripe from 'stripe';

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export async function handler(event) {
  const rawBody = event.body ?? '';
  const sig = event.headers['stripe-signature'] ?? '';
  let evt;
  try {
    evt = stripe.webhooks.constructEvent(rawBody, sig, process.env.STRIPE_WEBHOOK_SECRET);
  } catch (err) {
    return { statusCode: 400, body: `Webhook Error: ${err.message}` };
  }
  const isNew = await db.upsertWebhookEvent(evt.id, evt.type);
  if (!isNew) return { statusCode: 200, body: 'duplicate' };
  await global.durable.startNew('process-payment', undefined, { eventId: evt.id, type: evt.type, obj: evt.data.object });
  return { statusCode: 200, body: 'ok' };
}
webhooks/shopify.mjs
import { createHmac } from 'node:crypto';

export async function handler(event) {
  const rawBody = event.body ?? '';
  const hmac = event.headers['x-shopify-hmac-sha256'];
  const digest = createHmac('sha256', process.env.SHOPIFY_WEBHOOK_SECRET).update(rawBody).digest('base64');
  if (hmac !== digest) return { statusCode: 401, body: 'invalid signature' };
  const topic = event.headers['x-shopify-topic']; // e.g. 'orders/create'
  const order = JSON.parse(rawBody);
  await global.durable.startNew('process-order', undefined, { topic, orderId: order.id });
  return { statusCode: 200, body: '' };
}

Когда подходит

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

  • Интеграции SaaS
  • Платёжные провайдеры
  • События Git-хостинга

Когда лучше выбрать другое

  • Двунаправленные сокеты — другой транспорт

Частые вопросы

Почему сырое тело нельзя трогать до проверки HMAC?

Провайдеры подписывают точные байты; пересериализация JSON или смена кодировки меняет payload и даёт ложные ошибки «invalid signature».

Делать ли тяжёлую работу до ответа 200?

Нет — подтвердите получение в пределах таймаута провайдера, а работу продолжите в пайплайне или фоновой функции, чтобы ретраи не дублировали дорогие побочные эффекты.

Как обрабатывать повторные доставки вебхуков?

Считайте ID события у провайдера ключом идемпотентности: фиксируйте «уже видели» до изменения данных — тогда доставка «как минимум один раз» остаётся безопасной.