Inquir Compute logoInquir Compute
Сценарий · Inquir Compute

Обработка Stripe webhooks: HMAC, идемпотентность и async-пайплайн

Паттерн: raw body → HMAC SHA-256 → проверить ключ идемпотентности → ответить 200 → запустить пайплайн с бизнес-логикой. Stripe не делает повторную попытку, дублей нет.

Last updated: 2026-04-20

Direct answer

Обработка Stripe webhooks: HMAC, идемпотентность и async-пайплайн. Функция получает raw body в `event.body`, считает HMAC с секретом Stripe из переменных окружения, проверяет timing-safe, возвращает 200, вызывает `global.durable.startNew()`. Оркестрация делает бизнес-логику.

When it fits

  • Обработка занимает больше 2–3 секунд
  • Нужна идемпотентность при повторных доставках

Tradeoffs

  • Без timing-safe сравнения HMAC timing-атака может обойти проверку подписи.
  • Без ключа идемпотентности (`Stripe-Signature` header + event ID) повторная доставка создаёт дубль записи в базе.

Что ломается в простых Stripe webhook-обработчиках

  • Parsing body before HMAC verification: signature mismatch on valid events
  • Slow inline fulfillment: Stripe retries after 30s, causing double-charges
  • Missing idempotency key: retry delivers event twice, two orders created

Если обработчик парсит body до проверки подписи — атакующий может отправить произвольные события. Stripe требует raw body для HMAC.

Где теряются гарантии

Без timing-safe сравнения HMAC timing-атака может обойти проверку подписи.

Без ключа идемпотентности (`Stripe-Signature` header + event ID) повторная доставка создаёт дубль записи в базе.

Как Inquir закрывает каждый риск

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

Секрет endpoint хранится в переменных окружения — не в коде. Ротация без редеплоя.

Ключевые части Stripe webhook-обработчика

Raw body

Не парсить body до проверки подписи. `event.body` — строка, как она пришла от Stripe.

HMAC SHA-256

timing-safe сравнение с `stripe.webhooks.constructEvent()` или ручным crypto.timingSafeEqual.

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

Event ID из payload: `INSERT … ON CONFLICT DO NOTHING` до обработки.

Async оркестрация

`global.durable.startNew()` до `return { statusCode: 200 }` — бизнес-логика вне HTTP-окна.

Как обработать Stripe webhook

1

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

`stripe.webhooks.constructEvent(event.body, sig, secret)` — выбрасывает при несовпадении.

2

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

Записать event.id в базу; конфликт — вернуть 200 без работы.

3

Ответить 200 и запустить оркестрацию

`global.durable.startNew('fulfill', undefined, { eventId, type, data })` → `return { statusCode: 200 }`.

Stripe webhook handler

Complete pattern: raw body, signature verify, idempotency key, fast ACK, async fulfillment pipeline.

webhooks/stripe.mjs
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.webhookEvents.upsert({ id: evt.id, type: evt.type });
  if (!isNew) return { statusCode: 200, body: 'duplicate' };
  if (evt.type === 'payment_intent.succeeded') {
    await global.durable.startNew('stripe-fulfill', undefined, { intentId: evt.data.object.id, amount: evt.data.object.amount });
  }
  return { statusCode: 200, body: JSON.stringify({ received: true }) };
}

Когда нужен async пайплайн для Stripe

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

  • Обработка занимает больше 2–3 секунд
  • Нужна идемпотентность при повторных доставках

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

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

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

Почему нужен raw body?

Stripe считает подпись по raw-байтам тела. После JSON.parse/stringify байты могут измениться — подпись не совпадёт.

Как хранить webhook secret?

Переменная окружения `STRIPE_WEBHOOK_SECRET` в настройках функции. Не коммитить в git.

Inquir Compute logoInquir Compute

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

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

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