Webhook入口须立即返回200,验签需读原始body,幂等性依赖DB唯一索引而非内存Map,异步任务应交由Worker处理。

Webhook入口必须快速返回200,不能阻塞
Echo框架默认的HTTP handler是同步执行的,而Webhook回调要求「收到请求后立刻响应200,再异步处理业务逻辑」。如果在echo.Context里直接做耗时操作(比如调用外部API、查库、发消息),会导致第三方服务超时重试,甚至被拉黑。
常见错误现象:PayPal/Stripe/Agora等平台反复推送同一事件,日志里看到多次相同webhook:payment_completed任务;或者你本地用curl测试时卡住几秒才返回。
- 务必在
c.JSON(200, map[string]string{"status": "accepted"})或c.NoContent(200)之后再启动异步流程 - 不要用
go func() {...}()裸起协程——Echo的c对象不跨goroutine安全,且没上下文取消控制 - 推荐用
echorunner或标准context.WithTimeout包装后台任务,避免泄漏
签名验证必须读原始body,别碰c.FormValue
Webhook的签名(如X-Hub-Signature-256、Agora-Signature)是基于原始JSON body计算的。Echo默认会解析body为c.Request().Body,但一旦调用c.BodyBytes()或c.Bind(),底层io.ReadCloser就被消费掉了,后续验签会读到空内容。
正确做法是手动缓存原始字节:
body, _ := io.ReadAll(c.Request().Body)
c.Request().Body = io.NopCloser(bytes.NewBuffer(body)) // 恢复Body供Bind等使用
if !verifyHMAC(body, c.Request().Header.Get("X-Hub-Signature-256")) {
return c.String(401, "invalid signature")
}
- 所有主流平台(GitHub、Stripe、Agora、Judge0)都要求验签,跳过=开放攻击面
- 别用
c.FormValue或c.QueryParam读JSON数据,它们只对application/x-www-form-urlencoded有效 - 如果用
c.Bind()反序列化后再验签,必须先body = c.BodyBytes(),否则body已EOF
幂等性靠biz_id + DB唯一索引,不是靠内存Map
第三方服务可能因网络抖动重发同一Webhook(比如payment_intent.succeeded),你的接口必须能识别并丢弃重复请求。仅靠map[string]bool做内存去重完全不可靠——进程重启就清空,多实例部署直接失效。
开箱即用的技能链路由引擎。13 条预定义链覆盖搜索、开发、审查、MLOps、法律、创意等场景,三层路由架构(触发词→SAD反馈→DAG编排),recall@10=96.97%。配置驱动(chains.yaml),零代码扩展。pip install skill-weave-chains 一键安装。
可靠方案是把业务ID(如payment_intent_abc123)作为数据库主键或唯一索引字段:
INSERT INTO webhook_events (id, payload, status) VALUES (?, ?, 'pending') ON CONFLICT (id) DO NOTHING
- 从
body里提取稳定biz_id:GitHub用deliveryheader,Stripe用id字段,Agora用task_id - 别把敏感信息(密钥、token)塞进payload存DB,只存ID,worker查库补全详情
- 避免用
SELECT ... FOR UPDATE锁表,高并发下易成瓶颈;优先走唯一约束+ignore
异步任务交给Worker,别在HTTP handler里time.Sleep或长循环
有人试图在Echo handler里用time.Sleep(5 * time.Second)模拟异步,或写for i := 0; i ——这既不异步,也不可靠,还占着HTTP连接。
真正解耦的方式是「推队列 + 独立Worker」:
- Golang用
asynq:client.Enqueue(task, asynq.TaskID(bizID)),Worker里查DB判重再执行 - PHP用
Redis Queue或Supervisor管理worker进程 - Python用
celery或rq,handler里只发process_webhook.delay(biz_id)
复杂点在于:Webhook平台往往不保证投递顺序,但你的业务可能要求「支付成功后才发发货单」。这种依赖关系不能靠时间戳排序,得靠状态机+DB字段驱动。


















