GitHub 签名是如何生成的
GitHub 使用 Webhook Secret 作为 HMAC 密钥,对未经修改的请求 Body 计算 SHA-256,并把十六进制结果放在 X-Hub-Signature-256 请求头中。该值以 sha256= 开头。
import crypto from "node:crypto";
const expected = "sha256=" + crypto
.createHmac("sha256", process.env.GITHUB_WEBHOOK_SECRET)
.update(rawBody)
.digest("hex");
const valid = crypto.timingSafeEqual(
Buffer.from(expected),
Buffer.from(request.headers["x-hub-signature-256"] || "")
);签名不匹配的常见原因
- 框架先把 JSON 解析为对象,再重新序列化。
- 使用了错误仓库或组织的 Webhook Secret。
- 比较时遗漏
sha256=前缀。 - 读取 Body 时改变了编码、空格或换行。
- 直接用普通字符串比较,未使用恒定时间比较。
正确的排查顺序
- 保存收到的原始 Body 字节,不要先调用 JSON 解析器。
- 确认读取的是
X-Hub-Signature-256,而不是旧的 SHA-1 请求头。 - 在本地验签工具中输入相同 Secret、签名和 Body。
- 如果本地通过而应用失败,检查框架的 Body 中间件顺序。