验证 github webhook 需先读取原始 payload 字节,提取 x-hub-signature-256 头(去掉 sha256= 前缀并 hex 解码),用 secret 计算 hmac-sha256 签名比对;同时根据 x-github-event 和 action 字段路由事件、避免重复处理需校验 x-github-delivery 唯一 id 并持久化去重。

怎么验证 GitHub Webhook 请求的签名
GitHub 发来的每个 Webhook 请求都带 X-Hub-Signature-256 头,值是用你配置的 secret 对 payload 做 HMAC-SHA256 签名的十六进制字符串。不验签就直接处理,等于把接口敞开给任何人调用。
常见错误是:只比对 string(signature) 和自己算的字符串,但忽略了 GitHub 实际发送的是 sha256=xxx 格式;或者用了 http.Request.Body 两次(Body 是单次读取流,第二次读就是空)。
- 先用
io.ReadAll(r.Body)一次性读出原始 payload 字节,再用它验签、再用它解 JSON - 从
X-Hub-Signature-256头取值时,去掉前缀sha256=,再 hex 解码 - 签名计算用
hmac.New+sha256.New,key 是你 GitHub webhook 配置里填的secret字符串 - 验签失败必须返回
401或400,别沉默吞掉请求
怎么解析不同事件类型的 payload
GitHub 所有事件都走同一个 endpoint,但 X-GitHub-Event 头会标明类型(比如 push、pull_request、issues),payload 结构也完全不同。硬写一个 struct 去 decode 所有事件,很快就会 panic。
典型翻车点:用 json.Unmarshal 直接往一个大 struct 里塞,结果某个字段在 star 事件里是 string,在 fork 事件里是 object,直接报错;或者没检查 Action 字段(比如 pull_request 有 opened、closed、synchronize 等子状态)。
立即学习“go语言免费学习笔记(深入)”;
- 先读
X-GitHub-Event头,再按需选 struct —— 比如push用github.PushEvent(推荐用官方google/go-github的github.WebHookType工具函数) - 如果不用第三方库,至少为高频事件(
push、pull_request、issue_comment)各建一个 struct,字段用指针或interface{}容忍缺失 - 永远检查
Action字段是否符合预期,比如只响应pull_request.opened,就别碰synchronize
怎么避免重复处理同一事件
GitHub 可能因超时、网络问题重发请求,且重发请求的 X-Hub-Signature-256 和原始请求完全一致。如果你只靠验签通过就执行逻辑,很可能发两遍部署、建两个工单。
最容易被忽略的是:X-GitHub-Delivery 头才是唯一 ID(UUID 格式),不是 id 字段,也不是 payload 里的任何字段。而且这个 ID 在重试时不变。
- 把
X-GitHub-Delivery存进 Redis 或数据库,设个 1 小时 TTL(GitHub 文档说重试窗口约 30 分钟) - 收到请求先查这个 ID 是否已存在,存在就直接返回
200(GitHub 把2xx当作成功,不会继续重试) - 不要依赖 payload 里的时间戳或 commit hash 去去重 —— 不可靠,且无法覆盖重试场景
为什么不能直接用 http.HandleFunc 写死逻辑
简单 endpoint 很快会变臃肿:加日志要改主函数、换存储要改主函数、加新事件类型要改主函数。更麻烦的是,一旦某次处理 panic(比如 JSON 解析失败、空指针访问),整个 HTTP handler 就挂了,后续请求全 500。
真实线上环境里,你得隔离每类事件的执行上下文,还要能快速开关某类事件的处理,甚至临时降级。
- 用 map[string]func(*http.Request, []byte) 注册事件处理器,比如
handlers["pull_request"] = handlePullRequest - 每个 handler 函数内部用
defer+recover捕获 panic,记录错误但不中断服务 - 加一层中间件:统一做验签、读 body、存 delivery ID、打结构化日志 —— 主 handler 只剩纯业务逻辑
最麻烦的其实是 secret 管理和 delivery ID 的存储一致性:secret 别硬编码,delivery ID 的写入和业务逻辑得在同一个事务里(比如用 PostgreSQL 的 upsert),否则可能漏判重复。这些点不提前想清楚,上线后排查成本远高于写代码本身。










