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传递的可靠性与安全性。

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论
立即
投稿

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部