GH GambleHub

Гарантии доставки вебхуков

Вебхуки — асинхронные уведомления «из системы к подписчику» по HTTP(S). Сеть ненадежна: ответы теряются, пакеты приходят дубликатами или вне порядка. Поэтому гарантии доставки строятся не «по TCP», а на уровне протокола вебхуков и доменной идемпотентности.

Ключевая цель: обеспечить at-least-once доставку с порядком по ключу (там, где необходимо), дать подписчику материалы для идемпотентной обработки и инструмент reconcile для восстановлений.


1) Уровни гарантий

Best-effort — одноразовая попытка, без ретраев. Приемлемо только для «неважных» событий.
At-least-once (рекомендуется) — возможны дубликаты и out-of-order, но событие будет доставлено при условии доступности подписчика в разумный срок.
Effectively-exactly-once (на уровне эффекта) — достигается комбинацией идемпотентности и dedup-хранилища на стороне подписчика/отправителя. На транспорте HTTP «exactly-once» невозможно.


2) Контракт вебхука: минимально необходимое

Заголовки (пример):

X-Webhook-Id: 5d1e6a1b-4f7d-4a3d-8b3a-6c2b2f0f3f21  # глобальный ID события
X-Delivery-Attempt: 3                 # номер попытки
X-Event-Type: payment.authorized.v1          # тип/версия
X-Event-Time: 2025-10-31T12:34:56Z          # ISO8601
X-Partition-Key: psp_tx_987654            # ключ порядка
X-Seq: 418                      # монотонный номер по ключу
X-Signature-Alg: HMAC-SHA256
X-Signature: t=1730378096,v1=hex(hmac(secret, t        body))
Content-Type: application/json
Тело (пример):
json
{
"id": "5d1e6a1b-4f7d-4a3d-8b3a-6c2b2f0f3f21",
"type": "payment.authorized.v1",
"occurred_at": "2025-10-31T12:34:56Z",
"partition_key": "psp_tx_987654",
"sequence": 418,
"data": {
"payment_id": "psp_tx_987654",
"amount": "10.00",
"currency": "EUR",
"status": "AUTHORIZED"
},
"schema_version": 1
}

Требование к получателю: отвечать быстро `2xx` после буферизации и валидации подписи, а бизнес-обработку делать асинхронно.


3) Порядок и причинность

Порядок по ключу: гарантия «не уедет» только внутри одного `partition_key` (напр., `player_id`, `wallet_id`, `psp_tx_id`).
Глобальный порядок не гарантируется.
На стороне отправителя — очередь с сериализацией по ключу (один потребитель/шардинг), на стороне получателя — inbox с `(source, event_id)` и опционально ожидание пропущенных `seq`.

Если пропуски критичны — предоставьте pull-API `GET /events?after=checkpoint` для статуса «догнать и свериться».


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

Каждый вебхук несет стабильный `X-Webhook-Id`.
Получатель хранит `inbox(event_id)`: PK — `source + event_id`; повторы → no-op.
Побочные эффекты (запись в БД/кошелек) выполняются только один раз при первом «видении» события.
Для команд «с эффектом» используйте Idempotency-Key и кэш результатов на время окна ретраев.


5) Ретраи, backoff и окна

Политика ретраев (референс):
  • Ретраить на `5xx/timeout/connection error/409-Conflict (retryable)/429`.
  • Не ретраить на `4xx` кроме `409/423/429` (и только при согласованной семантике).
  • Экспоненциальный backoff + full jitter: 0.5s, 1s, 2s, 4s, 8s, … до `max=10–15 мин`; TTL окна ретраев: например, 72 часа.
  • Уважать `Retry-After` у получателя.
  • Иметь общий дедлайн: «признать событие не доставленным» и перевести его в DLQ.
yaml retry:
initial_ms: 500 multiplier: 2.0 jitter: full max_delay_ms: 900000 ttl: 72h retry_on: [TIMEOUT, 5xx, 429]

6) DLQ и redrive

DLQ — «кладбище» ядовитых или истекших по TTL событий с полной метаинформацией (пэйлоад, заголовки, ошибки, попытки, хэши).
Веб-консоль/API для redrive (точечная повторная доставка) с опциональной правкой endpoint/секрета.
Rate-limited redrive и batch-redrive с приоритезацией.


7) Безопасность

mTLS (по возможности) или TLS 1.2+.

Подпись тела (HMAC с секретом per tenant/endpoint). Верификация:

1. Извлечь `t` (timestamp) из заголовка, проверить скользящее окно (например, ±5 мин).

2. Восстановить строку подписи `tbody`, сравнить HMAC в constant-time.
Anti-replay: хранить `(event_id, t)` и отклонять слишком старые/повторные запросы.
Ротация секретов: поддержка двух активных секретов на период ротации.
Дополнительно: IP-allowlist, заголовок `User-Agent`, дедикация origin-IP.

8) Квоты, rate limits и справедливость

Fair-Queue per tenant/subscriber: чтобы один подписчик/тенант не забил общий пул.
Квоты и burst-лимиты на исходящий трафик и per-endpoint.
Реакция на `429`: чтить `Retry-After`, тротлить поток; при длительном лимитировании — degrade (отправка только критичных типов событий).


9) Жизненный цикл подписки

Register/Verify: POST endpoint → challenge/response или out-of-band подтверждение.
Lease (по желанию): подпись действует до `valid_to`; пролонгация — явная.
Secret rotation: `current_secret`, `next_secret` с `switch_at`.
Test ping: искусственное событие для проверки маршрута перед включением основных топиков.
Health-пробы: периодические HEAD/GET с проверкой latency и TLS профиля.


10) Эволюция схем (версии событий)

Версионирование типа события: `payment.authorized.v1` → `…v2`.
Эволюция — additive (новые поля → MINOR версии API), breaking → новый тип.
Регистр схем (JSON-Schema/Avro/Protobuf) + автоматическая валидация перед отправкой.
Заголовок `X-Event-Type` и поле `schema_version` в теле — оба обязательны.


11) Наблюдаемость и SLO

Метрики (по типу/тенанту/подписчику):
  • `deliveries_total`, `2xx/4xx/5xx_rate`, `timeout_rate`, `signature_fail_rate`.
  • `attempts_avg`, `p50/p95/p99_delivery_latency_ms` (от публикации до 2xx).
  • `dedup_rate`, `out_of_order_rate`, `dlq_rate`, `redrive_success_rate`.
  • `queue_depth`, `oldest_in_queue_ms`, `throttle_events`.
SLO (референс):
  • Доля доставок ≤ 60 c (p95) — 99.5% для критичных событий.
  • DLQ ≤ 0.1% за 24 ч.
  • Signature failures ≤ 0.05%.

Логи/трейсинг: `event_id`, `partition_key`, `seq`, `attempt`, `endpoint`, `tenant_id`, `schema_version`, `trace_id`.


12) Референсный алгоритм отправителя

1. Записать событие в транзакционный outbox.
2. Определить partition_key и seq; поместить в очередь.
3. Воркер берет по ключу, формирует запрос, подписывает, отправляет с таймаутами (connect/read).
4. При `2xx` — признать доставленным, зафиксировать латентность и seq-чекпоинт.
5. При `429/5xx/timeout` — ретрай согласно политике.
6. По TTL → DLQ и алерт.


13) Референсный обработчик (получатель)

1. Принять запрос, проверить TLS/proto.
2. Валидация подписи и окна времени.
3. Быстрый ACK 2xx (после синхронной записи в локальный inbox/очередь).
4. Асинхронный воркер читает `inbox`, проверяет `event_id` (дедуп), при необходимости — упорядочивает по `seq` внутри `partition_key`.
5. Выполняет эффекты, пишет «offset/seq чекпоинт» для reconcile.
6. В случае ошибки — локальные ретраи; «ядовитые» задачи → локальная DLQ с алертами.


14) Reconcile (пулл-контур)

Для «непробиваемых» инцидентов:
  • `GET /events?partition_key=…&after_seq=…&limit=…` — отдать все пропущенные.
  • Токен-чекпоинт: `after=opaque_token` вместо seq.
  • Идемпотентный redelivery: те же `event_id`, та же подпись по новому `t`.

15) Полезные заголовки и коды

2xx — принял (даже если бизнес-обработка позже).
410 Gone — endpoint закрыт (отправитель прекращает доставку и помечает подписку как «в архив»).
409/423 — временная блокировка ресурса → ретрай разумен.
429 — слишком часто → троттл и backoff.
400/401/403/404 — конфигурационная ошибка; остановить ретраи, открыть тикет.


16) Мульти-тенант и регионы

Отдельные очереди и лимиты per tenant/endpoint.
Data residency: отправка из региона данных; сквозные заголовки `X-Tenant`, `X-Region`.
Изоляция сбоев: падение одного подписчика не влияет на остальных (separate pools).


17) Тестирование

Contract tests: фиксированные примеры тел/подписей, проверка валидации.
Chaos: дроп/дубликаты, shuffle порядка, задержки сети, `RST`, `TLS`-ошибки.
Load: burst-шторма, замер p95/p99.
Security: анти-реплей, устаревшие timestamp, неправильные секреты, ротация.
DR/Replay: массовый redrive из DLQ в изолированном стенде.


18) Плейбуки (runbooks)

1. Рост `signature_fail_rate`

Проверить дрейф часов, истекший `tolerance`, ротацию секретов; временно включить «dual secret».

2. Очередь стареет (`oldest_in_queue_ms` ↑)

Увеличить воркеры, включить приоритизацию критичных топиков, временно понизить частоту «шумных» типов.

3. Шторм `429` у подписчика

Включить троттлинг и паузы между попытками; сдвинуть менее критичные типы событий.

4. Массовые `5xx`

Открыть circuit-breaker для конкретного endpoint, перевести в режим defer & batch; сигнал подписчику.

5. Заполнение DLQ

Остановить не-критичную публикацию, включить batch-redrive с низким RPS, поднять алерты владельцам подписок.


19) Типичные ошибки

Синхронная тяжелая обработка до ответа 2xx → ретраи и дубликаты.
Нет подписи тела/окна времени → уязвимость к подмене/реплею.
Отсутствие `event_id` и `inbox` → невозможно сделать идемпотентность.
Попытка «глобального порядка» → вечные блокировки очередей.
Ретраи без jitter/лимитов → усиление инцидента (thundering herd).
Единый общий пул на всех подписчиков → «шумный» кладет всех.


20) Чек-лист перед продом

  • Контракт: `event_id`, `partition_key`, `seq`, `event_type.vN`, подпись HMAC и timestamp.
  • Отправитель: outbox, сериализация по ключу, ретраи с backoff+jitter, TTL, DLQ и redrive.
  • Получатель: быстрая запись в inbox + 2xx; идемпотентная обработка; локальная DLQ.
  • Безопасность: TLS, подписи, анти-реплей, dual-secret, ротация.
  • Квоты/лимиты: fair-queue per tenant/endpoint, уважение `Retry-After`.
  • Reconcile API и чекпоинты; документация для подписчиков.
  • Наблюдаемость: p95/потоки/ошибки/DLQ, трассировка по `event_id`.
  • Версионирование событий и политика эволюции схем.
  • Плейбуки инцидентов и «кнопка» глобального пауза/разморозки.

Заключение

Надежные вебхуки — это протокол поверх HTTP, а не просто «POST с JSON». Четкий контракт (ID, ключ порядка, подпись), идемпотентность, ретраи с jitter, справедливая очередь и хорошо отлаженные плейбуки превращают «лучший-случай» в предсказуемый и измеряемый механизм доставки. Стройте at-least-once + порядок по ключу + reconcile, и система спокойно переживет сеть, пики нагрузки и человеческие ошибки.

Contact

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

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

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

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

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

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