正确接收 Webhook 需:1. 用 http.HandleFunc 注册路由并显式检查 r.Method == "POST";2. 调用 io.ReadAll(r.Body) 一次性读取原始 body;3. 校验签名(如 GitHub 的 X-Hub-Signature-256)须在读 body 后立即用 hmac.Equal 比对;4. 响应仅 w.WriteHeader(http.StatusOK),不写任何 body;5. 解析 JSON 时用指针结构体、json.RawMessage 和类型化错误处理;6. 业务逻辑必须异步执行,避免阻塞 HTTP handler。
怎么用
正确接收 Webhook 请求
Webhook 本质就是 HTTP POST 请求,Golang 用标准库就能接,但很多人卡在解析体、校验签名、响应状态这几个环节。别写成 REST API 那套逻辑——Webhook 不需要返回业务数据,只管快速回
,否则第三方会重试甚至拉黑你的地址。
常见错误现象:
(注册了
路由却收
)、
(没设
但强行用
)、
(body 被多次读取)。
路由必须显式注册
方法,或直接用
+
拦截
务必调用
或
一次性读完 body,后续不能再读
第三方通常要求响应头不含多余字段,用
+ 空 body 即可,别用
如果要校验签名(比如 GitHub 的
),必须在读 body 后立即计算,不能等 decode 完再算——因为
会吞掉原始字节流
如何安全解析 JSON Webhook 并避免 panic
Golang 的
对非法输入很敏感,而 Webhook 来源不可控。直接传空指针或结构体字段类型错,就会 panic;更隐蔽的是嵌套 map 或未知字段导致解码失败但不报错。
使用场景:GitHub、Stripe、微信支付等回调都带固定字段,但可能新增可选字段,或某些字段值为
。
立即学习
“
go语言免费学习笔记(深入)
”;
永远用指针接收结构体:
,而不是
,避免零值覆盖
关键字段加
,非关键字段用
接住,留待后续按需解析
检查
且区分类型:
表示格式错,
表示类型不匹配,别全丢给
对时间字段,优先用
+ 自定义
方法处理字符串/数字混输,别依赖默认解析
为什么 Webhook 处理要拆成「接收」和「异步执行」两步
第三方系统发 Webhook 时,通常有超时限制(常见 10s),你如果在 handler 里连数据库、调外部 API、做复杂计算,很容易超时导致重发。这不是并发问题,是职责隔离问题——HTTP 层只负责“签收”,业务逻辑必须扔进 goroutine 或消息队列。
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
性能影响:goroutine 开销小,但没做限流的话,突发大量 Webhook 可能打爆内存;用 channel 做缓冲又容易堵死。
简单场景用带缓冲的 channel + 固定 worker 数量,比如
,启动 3 个 goroutine 消费
生产环境建议直接投递到
或
,避免进程重启丢事件
千万别在 goroutine 里直接用
——
是上层 handler 的,goroutine 里已关闭
记录原始 payload 到日志或 DB 时,用
,别用
,后者可能 panic 或泄露指针
如何验证 Webhook 签名(以 GitHub HMAC-SHA256 为例)
签名不是可选项,是安全底线。GitHub 用
头传入
,你得用相同密钥和原始 body 算一遍比对。错一点就全错——空格、换行、编码差异都会让哈希值不同。
容易踩的坑:
返回的是带
前缀的字符串,得先切掉;body 必须是未被修改的原始字节,不能是 decode 后再 marshal 出来的。
用
+
计算,密钥用
,别用字符串直接转
签名比对必须用
(防时序攻击),别用
如果第三方文档写“使用 secret 加密 body”,注意它说的 “body” 是指原始 HTTP body 字节,不是 JSON 解析后的对象
测试时用
手动构造,别依赖前端工具自动加 header
最麻烦的从来不是写接收逻辑,而是第三方文档没写清 body 编码方式、签名是否包含换行、时间字段是秒还是毫秒、重试策略怎么配。上线前一定拿真实回调流量做端到端验证,别信模拟请求。
http.HandleFunc200 OK405 Method Not AllowedGETPOST400 Bad RequestContent-Type: application/jsonjson.DecodeEOFPOSThttp.HandleFuncif r.Method != "POST"r.ParseForm()io.ReadAll(r.Body)w.WriteHeader(http.StatusOK)json.NewEncoder(w).Encode(...)X-Hub-Signature-256json.Decodejson.Unmarshalnullvar payload *GitHubPushEventvar payload GitHubPushEventjson:"field_name,omitempty"json.RawMessageerrjson.SyntaxErrorjson.UnmarshalTypeErrorlog.Fataltime.TimeUnmarshalJSONch := make(chan []byte, 100)RabbitMQKafkadefer r.Body.Close()r.Bodystring(bodyBytes)fmt.Sprintf("%v", payload)X-Hub-Signature-256sha256=xxxreq.Header.Get("X-Hub-Signature-256")sha256=hmac.Newsha256.New[]bytehmac.Equal==curl -H "X-Hub-Signature-256: sha256=..." -d '{"test":1}' http://localhost/webhook