Webhook回调重试与幂等设计实战概述Webhook传递在网络抖动与目标端故障下需具备去重、重试与回退能力,确保事件不丢与不重复处理。幂等键与去重窗口class IdempotencyStore {
ttlMs: number
map = new Map<string, number>()
constructor(ttlMs: number) { this.ttlMs = ttlMs }
set(key: string): void { this.map.set(key, Date.now() + this.ttlMs) }
seen(key: string): boolean {
const exp = this.map.get(key)
if (!exp) return false
if (Date.now() > exp) { this.map.delete(key); return false }
return true
}
}
function buildIdempotencyKey(eventId: string, bodyHash: string): string {
return `${eventId}:${bodyHash}`
}
指数退避重试async function retryWithBackoff(fn: () => Promise<Response>, maxAttempts: number, baseMs: number): Promise<Response> {
let attempt = 0
while (attempt < maxAttempts) {
const res = await fn()
if (res.ok) return res
attempt++
const wait = Math.min(30_000, baseMs * Math.pow(2, attempt))
await new Promise(r => setTimeout(r, wait))
}
throw new Error("delivery_failed")
}
状态码约定2xx:成功接收并完成处理409:幂等键重复,视为已处理429/503:建议重试,走退避策略4xx其他:不重试并记录失败发送端实现async function deliver(url: string, payload: string, idKey: string, store: IdempotencyStore): Promise<Response> {
const bodyHash = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(payload))
const key = buildIdempotencyKey(idKey, Array.from(new Uint8Array(bodyHash)).join(""))
const fn = () => fetch(url, { method: "POST", headers: { "Content-Type": "application/json", "X-Idempotency-Key": key }, body: payload })
return await retryWithBackoff(fn, 5, 500)
}
接收端实现async function handleWebhook(req: { headers: Record<string, string>; body: string }, store: IdempotencyStore): Promise<{ status: number }> {
const key = req.headers["x-idempotency-key"]
if (!key) return { status: 400 }
if (store.seen(key)) return { status: 409 }
store.set(key)
const ok = await processEvent(req.body)
return { status: ok ? 200 : 503 }
}
运维要点对重试次数、窗口命中与失败原因进行指标化与告警幂等键采用事件ID与内容哈希组合以抵御重复与篡改对重复事件保持幂等响应,避免副作用以上机制可在常见平台对接场景中提升Webhook传递的可靠性与安全性。

发表评论 取消回复