Webhooks和事件的平均水平
TL;DR
良好的webhook是簽名(HMAC/mTLS),總結和偶發事件,是通過指數回程和收件人重復數據消除的at-least-once模型提供的。商定信封(「event_id」,「type」,「ts」,「version」,「attempt」,「signature」),時間窗口(≤5分鐘),響應代碼,撤回,DLQ和狀態終止。
1)角色和交付模式
發件人(您是/提供商):形成事件、簽名、嘗試傳送至2xxx、在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」。
進化規則:添加字段;刪除/更改類型-僅使用新的「版本」。
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-Ivent-Id」以前未處理(dedup)
- 「X-Signature」匹配(與超時安全比較)
3.2 Dop。措施
IP/ASN allow-list.
mTLS用於高度敏感的webhook。
如果webhook觸發回調,則用於sender-constrained的DPoP(可選)。
4)相似性和重復數據消除
4.1事件的冪等性
具有相同「event_id」的事件不應再次更改狀態。收件人:- 將「event_id」存儲在TTL ≥ 24-72小時的等效腰包(KV/Redis/DB)中;
- 保留處理結果(成功/錯誤,工件)以重新返回。
4.2指令的冪等(回調)
如果webhook導致客戶端使用API(例如「確認付款」),請在REST調用卷上使用「Idempotency-Key」,將結果存儲在服務端(exactly-once outcome)。
KV模型(最低):
key: idempotency:event:01HF7...
val: { status: "ok", processed_at: "...", handler_version: "..." }
TTL: 3d
5)Retrai和backoff
推薦的圖形(帶有抖動的指數圖):- 5 s、15 s、30 s、1 m、2 m、5 m、10 m、30 m、1 h、3 h、6 h、12 h、24 h"(更遠的是每日到N天)
- 2 xx是成功的,停止回程。
- 4xx:
「400/ 401/403/404/422」-如果簽名/格式為ok(客戶端錯誤),則不重復。
「429」是「Retry-After」或backoff的復制品。
5 x/network-retraim。
發件人標題:「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發件人的Endpoint
「POST/webhooks/replay」-通過「event_id」列表或時間窗口。
「GET/webhooks/events/:id」-顯示源軟件包和嘗試歷史記錄。
DLQ:「死」事件(retrais限制用盡)→單獨的存儲,alerta。
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, retrai/事件, drop-rate DLQ, share 2xx/4xx/5xx,延遲窗口高達2xx。
- verify fail rate(簽名/時間),dup-rate, latency handler p95, 5xx。
- 交付:≥ 99。9%的事件獲得2xx <3 c p95(在首次成功嘗試之後)。
- 加密反駁:簽名驗證≤ 2-5 ms p95。
- Dedoop: 0重復效果(在域級別上只出現一個結果)。
11)數據安全和隱私
不要在webhook體內傳播PAN/PII;通過授權的API在部件後面使用ID和後續拉動。
在日誌中掩蓋敏感字段;使用TTL將事件主體保持在最低限度。
加密DLQ存儲和繼電器。
12)轉化與兼容性
「版本」(信封)和路徑中的版本:「/webhooks/v1/payments」。
新字段-可選;刪除-僅在「日落」期之後。
記錄對machine-readable changelog(用於自動反駁)的更改。
13)測試案例(UAT支票單)
- 重新交付相同的'event_id' →一個效果和'200'復制。
- 標題:正確的鑰匙,不正確的鑰匙,舊鑰匙(輪換),「X-Timestamp」窗外。
- Backoff:收件人給出'429'的'Retry-After' →正確的暫停。
- 順序:「……處理」事件早於「……created」 →正確處理/等待。
- 收件人的DB在效果和「mark_seen」之間發生故障→原子/重復。
- DLQ和手動復制→成功交付。
- 大規模的「風暴」(包裹縫合提供者)→沒有損失,限制不會扼殺關鍵。
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」答案→崩潰事件丟失。
將安全細節「沖洗」到響應/logi中。
缺少DLQ/反射-事件無法解決。
16)施帕加爾卡實施
Конверт: `event_id`, `type`, `version`, `ts`, `attempt`, `data`.
安全:HMAC v1+「X-Timestamp」+「X-Ivent-Id」,窗口≤ 5分鐘;根據需要,mTLS/IP allow-list。
交付:上空,背靠背,「Retry-After」、DLQ+replay API。
相似性:KV-kesh 24-72小時,效應原子固定+'mark_seen'。
可觀察性:交付,簽名,重復指標;「trace_id」跟蹤。
文件:版本,響應代碼,示例,UAT支票清單。
二.總結
持久的Webhooks建在三條鯨魚上:簽名信封,近距離交付和偶數處理。正式化合同,啟用HMAC/mTLS和時間窗口,實現retrais+DLQ和恢復,存儲等效標簽,並捕獲原子效應。然後,即使網絡故障,負載峰值和罕見的「命運重復」,事件仍保持可靠。