GH GambleHub

Webhooks и идемпотентность событий

TL;DR

Хороший вебхук — это подписанное (HMAC/mTLS), резюмируемое и идемпотентное событие, доставляемое по модели at-least-once с экспоненциальным backoff и дедупликацией у получателя. Договоритесь о конверте (`event_id`, `type`, `ts`, `version`, `attempt`, `signature`), окне времени (≤5 мин), кодах ответов, ретраях, DLQ и статус-эндпоинте.


1) Роли и модель доставки

Отправитель (вы/провайдер): формирует событие, подписывает, пытается доставить до 2xx, ретраит при 3xx/4xx/5xx (кроме явных «не принимай»), ведет DLQ, дает replay API.
Получатель (партнер/ваш сервис): проверяет подпись/временное окно, делает дедуп и идемпотентную обработку, отвечает корректным кодом, предоставляет /status и /ack replay по `event_id`.

Гарантии: at-least-once. Получатель должен уметь обрабатывать дубликаты и смену порядка.


2) Конверт события (envelope)

json
{
"event_id": "01HF7H9J9Q3E7DYT5Y6K3ZFD6M",
"type": "payout.processed",
"version": "2025-01-01",
"ts": "2025-11-03T12:34:56.789Z",
"attempt": 1,
"producer": "payments",
"tenant": "acme",
"data": {
"payout_id": "p_123",
"status": "processed",
"amount_minor": 10000,
"currency": "EUR"
}
}

Обязательные поля: `event_id`, `type`, `version`, `ts`, `attempt`.
Правила эволюции: добавляем поля; удаление/смена типов — только с новой `version`.


3) Безопасность: подписи и привязка

3.1 HMAC-подпись (рекомендуется по умолчанию)

Заголовки:

X-Signature: v1=base64(hmac_sha256(<secret>, <canonical>))
X-Timestamp: 2025-11-03T12:34:56Z
X-Event-Id: 01HF7...
Каноническая строка:

<timestamp>\n<method>\n<path>\n<sha256(body)>
Проверка у получателя:
  • abs(now − `X-Timestamp`) ≤ 300s
  • `X-Event-Id` не обработан ранее (дедуп)
  • `X-Signature` совпадает (с тайм-безопасным сравнением)

3.2 Доп. меры

mTLS для высокочувствительных вебхуков.
IP/ASN allow-list.
DPoP (опционально) для sender-constrained, если вебхук инициирует обратные вызовы.


4) Идемпотентность и дедупликация

4.1 Идемпотентность события

Событие с одинаковым `event_id` не должно менять состояние повторно. Получатель:
  • хранит `event_id` в идемпотентном кеше (KV/Redis/БД) на TTL ≥ 24–72 ч;
  • сохраняет результат обработки (успех/ошибка, артефакты) для повторной отдачи.

4.2 Идемпотентность команд (обратные вызовы)

Если вебхук заставляет клиента дернуть API (например, «подтверди payout»), используйте `Idempotency-Key` на том REST-вызове, храните результат на стороне сервиса (exactly-once outcome).

KV-модель (минимум):

key: idempotency:event:01HF7...
val: { status: "ok", processed_at: "...", handler_version: "..." }
TTL: 3d

5) Ретраи и backoff

Рекомендуемый график (экспоненциальный с джиттером):
  • `5s, 15s, 30s, 1m, 2m, 5m, 10m, 30m, 1h, 3h, 6h, 12h, 24h` (дальше ежедневные до N дней)
Решения по кодам:
  • 2xx — успех, прекратить ретраи.
4xx:
  • `400/401/403/404/422` — не ретраим, если подпись/формат ок (клиентская ошибка).
  • `429` — ретраим по `Retry-After` или backoff.
  • 5xx/сетевые — ретраим.

Заголовки отправителя: `User-Agent`, `X-Webhook-Producer`, `X-Attempt`.


6) Обработка на стороне получателя

Псевдопайплайн:
pseudo verify_signature()
if abs(now - X-Timestamp) > 300s: return 401

if seen(event_id):
return 200 // идемпотентный ответ

begin transaction if seen(event_id): commit; return 200 handle(data)       // доменная логика mark_seen(event_id)    // запись в KV/DB commit return 200

Транзакционность: метка «seen» должна ставиться атомарно с эффектом операции (или после фиксации результата), чтобы избежать двойной обработки при сбое.


7) Гарантии порядка и снапшоты

Порядок не гарантируется. Используйте `ts` и доменные `seq`/`version` в `data` для сверки актуальности.
Для длинных лагов/потерь — добавьте /replay у отправителя и /resync у получателя (получить снапшот и дельты по окну времени/ID).


8) Статус, replay и DLQ

8.1 Эндпоинты отправителя

`POST /webhooks/replay` — по списку `event_id` или по окну времени.
`GET /webhooks/events/:id` — показать исходный пакет и историю попыток.
DLQ: «мертвые» события (исчерпан лимит ретраев) → отдельное хранилище, алерты.

8.2 Эндпоинты получателя

`GET /webhooks/status/:event_id` — `seen=true/false`, `processed_at`, `handler_version`.
`POST /webhooks/ack` — (опционально) подтверждение ручной обработки из DLQ.


9) Контракты ошибок (ответ получателя)

http
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
Retry-After: 120
X-Trace-Id: 4e3f...

{
"error": "invalid_state",
"error_description": "payout not found",
"trace_id": "4e3f..."
}

Рекомендации: всегда возвращайте четкий код и, если можно, `Retry-After`. Не возвращайте подробные детали безопасности.


10) Мониторинг и SLO

Метрики (отправитель):
  • delivery p50/p95, success rate, ретраи/событие, drop-rate DLQ, share 2xx/4xx/5xx, окно задержки до 2xx.
Метрики (получатель):
  • verify fail rate (подпись/время), dup-rate, latency handler p95, 5xx.
SLO ориентиры:
  • Доставка: ≥ 99.9% событий получают 2xx < 3 c p95 (после первой успешной попытки).
  • Криптопроверка: валидация подписи ≤ 2–5 мс p95.
  • Дедуп: 0 повторных эффектов (exactly-once outcome на уровне домена).

11) Безопасность данных и приватность

Не передавайте PAN/PII в теле вебхука; используйте идентификаторы и последующий pull за деталями по авторизованному API.
Маскируйте чувствительные поля в логах; храните тела событий только по минимуму, с TTL.
Шифруйте хранилища DLQ и реплея.


12) Версионирование и совместимость

Версия в `version` (конверт) и в пути: `/webhooks/v1/payments`.
Новые поля — опциональны; удаление — только после периода `Sunset`.
Документируйте изменения в machine-readable changelog (для автопроверок).


13) Тест-кейсы (UAT чек-лист)

  • Повторная доставка того же `event_id` → один эффект и `200` на дубликаты.
  • Подпись: верный ключ, неверный ключ, старый ключ (ротация), `X-Timestamp` вне окна.
  • Backoff: получатель дает `429` с `Retry-After` → корректная пауза.
  • Порядок: события `…processed` приходит раньше `…created` → корректная обработка/ожидание.
  • Сбой БД у получателя между эффектом и `mark_seen` → атомарность/повтор.
  • DLQ и ручной replay → успешная доставка.
  • Массовый «шторм» (провайдер шлет пачки) → без потери, лимиты не душат критичное.

14) Мини-сниппеты

Подпись отправителя (псевдо):
pseudo body = json(event)
canonical = ts + "\n" + "POST" + "\n" + path + "\n" + sha256(body)
sig = base64(hmac_sha256(secret, canonical))
headers = {"X-Timestamp": ts, "X-Event-Id": event.event_id, "X-Signature": "v1="+sig}
POST(url, body, headers)
Проверка и дедуп получателя (псевдо):
pseudo assert abs(now - X-Timestamp) <= 300 assert timingSafeEqual(hmac(secret, canonical), sig)

if kv.exists("idemp:"+event_id): return 200

begin tx if kv.exists("idemp:"+event_id): commit; return 200 handle(event.data)        // доменная логика kv.set("idemp:"+event_id, "ok", ttl=259200)
commit return 200

15) Частые ошибки

Нет дедупа → повторные эффекты (двойные рефанды/пэйауты).
Подпись без таймштампа/окна → уязвимость к replay.
Хранение одного HMAC-секрета на всех партнеров.
Ответы `200` до фиксации результата → потеря событий при крэше.
«Вымывание» деталей безопасности в ответы/логи.
Отсутствие DLQ/реплея — инциденты нерешаемы.


16) Шпаргалка внедрения

Безопасность: HMAC v1 + `X-Timestamp` + `X-Event-Id`, окно ≤ 5 мин; мTLS/IP allow-list по надобности.
Конверт: `event_id`, `type`, `version`, `ts`, `attempt`, `data`.
Доставка: at-least-once, backoff с джиттером, `Retry-After`, DLQ + replay API.
Идемпотентность: KV-кеш 24–72 ч, атомарная фиксация эффекта + `mark_seen`.
Наблюдаемость: метрики доставки, подписи, дубликатов; трассировка `trace_id`.
Документация: версия, коды ответов, примеры, UAT-чек-лист.


Резюме

Стойкие вебхуки строятся на трех китах: подписанный конверт, at-least-once доставка и идемпотентная обработка. Формализуйте контракт, включите HMAC/mTLS и окно времени, реализуйте ретраи+DLQ и реплей, храните идемпотентные метки и фиксируйте эффекты атомарно. Тогда события остаются надежными даже при сбоях сети, пиках нагрузки и редких «дубликатах судьбы».

Contact

Свяжитесь с нами

Обращайтесь по любым вопросам или за поддержкой.Мы всегда готовы помочь!

Начать интеграцию

Email — обязателен. Telegram или WhatsApp — по желанию.

Ваше имя необязательно
Email необязательно
Тема необязательно
Сообщение необязательно
Telegram необязательно
@
Если укажете Telegram — мы ответим и там, в дополнение к Email.
WhatsApp необязательно
Формат: +код страны и номер (например, +380XXXXXXXXX).

Нажимая кнопку, вы соглашаетесь на обработку данных.