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.
  • 5хх/мережеві - ретраїм.

Заголовки відправника: `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).

Натискаючи кнопку, ви погоджуєтесь на обробку даних.