Skip to content
PПромтбук
RUEN
04Архитектура

Дизайн webhooks: payload, подпись, retry, идемпотентность

Полный дизайн исходящих webhooks: схема payload, HMAC-подпись, политика ретраев, идемпотентность, защита от replay, observability, dead letter, юзер-debug.

Действуй как senior backend архитектор. Спроектируй исходящие webhooks для событий {{event_types}}. Стек: {{stack}}, объём: {{scale}} events/day.

Что должно быть в дизайне

1. Payload schema

  • Стабильная версия (v1) в URL или заголовке X-Webhook-Version. Никаких breaking changes без v2.
  • Обязательные поля: id (UUID события), type (e.g. order.created), created_at (ISO 8601 UTC), data (объект).
  • api_version отдельно от data schema version.
  • data — snapshot на момент события, не "текущее состояние". Webhook доехал через час — у нас в БД уже могло измениться.
  • Размер payload — фикс ≤ 256KB. Большие объекты — { "object_url": "https://api/...?id=X" } чтобы получатель забрал сам.

2. HMAC signing

  • Алгоритм: HMAC-SHA256.
  • Заголовки: X-Webhook-Signature: t=<timestamp>,v1=<hex> (Stripe-style — позволяет ротацию схем).
  • Подписываемая строка: {timestamp}.{raw_body}. timestamp в заголовке защищает от replay.
  • Secret per endpoint, ротация — два активных secret'а одновременно (overlap window 24-48 часов).
  • В docs дать готовый verify-snippet на 3-4 языках (Node, Python, Go, Ruby).

3. Retry policy

  • Exponential backoff с jitter: 1m → 5m → 30m → 2h → 6h → 24h, max 6 attempts, итого ~32 часа.
  • Retry только на: network error, timeout, HTTP 5xx, HTTP 429.
  • 4xx (кроме 429) — не retry, помечаем как permanently failed, в dead letter.
  • 2xx (любой) — success.
  • Timeout на одну попытку — 10 секунд (а не 30: медленный consumer не должен блокировать твою очередь).

4. Idempotency на стороне получателя

  • X-Webhook-Id: <uuid> уникален per delivery attempt. У одного события — один id, повторные попытки несут тот же id.
  • В docs явно: "consumer должен дедуплицировать по id (хранить минимум 7 дней)".
  • На стороне sender — exactly-once delivery невозможно; обеспечиваем at-least-once + id для idempotency.

5. Replay protection

  • Timestamp в подписи + строгая валидация: ±5 минут от now(). Старше — отбрасывать (это уже не webhook, это атака).
  • Документировать: получатель обязан проверять timestamp, не только подпись.

6. Observability

  • Per-endpoint метрики: delivery_attempts, success_rate, p50/p95/p99 latency, failure_rate by status_code.
  • UI для юзера (dashboard эндпоинта): список последних 100 deliveries с raw request/response, status, retry count, next retry time.
  • Алерт получателю (email/in-app), если success rate за 24h < 80% — "что-то у вас сломалось".

7. Dead letter / failure handling

  • После 6 failed attempts → DLQ + статус endpoint'а disabled если 100 DLQ подряд.
  • Юзер может вручную replay конкретное событие из UI или через API: POST /webhooks/events/{id}/redeliver.
  • DLQ хранится 30 дней, потом purge.

8. User-debug experience

  • В UI: текущий status подписи (signing secret последней ротации), пример verify-кода на их языке.
  • "Test endpoint" кнопка — отправляет synthetic event webhook.test с известным payload, юзер видит full request/response inline.
  • При неуспехе показать не "Failed" а конкретно: HTTP 401 / timeout after 10s / TLS handshake failed: <reason>.

Формат вывода

## Payload schema
```json
{ ... пример ... }

Signature spec

<алгоритм + пример заголовков>

Retry table

| Attempt | Delay | Timeout | | 1 | 0 | 10s | ...

API surface

  • POST /webhooks/endpoints (create)
  • GET /webhooks/endpoints/{id}/deliveries (list)
  • POST /webhooks/events/{id}/redeliver ...

Receiver pseudocode (3 языка)


## Anti-patterns

- ❌ Подписывать не raw body, а распарсенный JSON — receiver не воспроизведёт байты (whitespace, key ordering) → подпись не сойдётся.
- ❌ Retry на 4xx — DDoS получателя, который вернул "не надо мне это".
- ❌ Timeout 60s на доставку — медленный consumer кладёт весь queue.
- ❌ Без timestamp в подписи → replay-атака навсегда валидна.
- ❌ Менять payload без версии → у всех existing consumers ломается parsing.
- ❌ "Exactly once" обещание → невыполнимо, лучше честно at-least-once + id для дедупликации.
- ❌ Без UI для debug → саппорт-тикеты "почему не пришло" забивают inbox.
- ❌ Без dead letter → потерянные события невозможно восстановить.
К подразделу «Архитектура»
Похожие промты