PROVIDER RULES
X-Hub-Signature-256
HMAC-SHA256(raw_body),Hex 前缀 sha256=
- 对未改动的 UTF-8 Body 计算 HMAC-SHA256。
- Header 值必须带 sha256= 前缀。
- 不要再使用旧的 X-Hub-Signature / SHA-1。
WEBHOOK / HMAC / RAW BODY
按服务商真实规则复现签名,区分签名字节、Header 格式和时间窗口,定位“本地算得一样、服务端却验不过”的问题。
已载入 GitHub 官方文档的已知签名样例,可直接点击验签。
X-Hub-Signature-256
这里的换行、空格、转义和键顺序都会参与签名。请粘贴框架解析 JSON 之前读到的原始正文,不要格式化后再验。
PROVIDER RULES
HMAC-SHA256(raw_body),Hex 前缀 sha256=
NODE.JS REFERENCE
import {createHmac, timingSafeEqual} from "node:crypto";
const rawBody = await request.text();
const received = request.headers.get("x-hub-signature-256") ?? "";
const expected = "sha256=" + createHmac("sha256", process.env.GITHUB_WEBHOOK_SECRET)
.update(rawBody, "utf8")
.digest("hex");
const valid = received.length === expected.length &&
timingSafeEqual(Buffer.from(received), Buffer.from(expected));RELATED TOOLS
USE & REVIEW
服务商签名的是收到时那串确定的字节。先把 JSON 解析成对象再 stringify,可能改变空格、转义、字段顺序和结尾换行,哪怕内容语义相同,HMAC 也会完全不同。服务端应在 Body Parser 改写之前保存 raw body,再按对应服务商规则验签。
它们虽然常用 HMAC-SHA256,但签名原文和输出格式不同。GitHub 对原始 Body 计算 Hex 并加 sha256=;Shopify 输出 Base64;Stripe 在 Body 前拼时间戳和句点;Slack 拼成 v0:timestamp:raw_body。算法名称相同不代表协议可以互换。
这说明 Secret、正文和签名算法能复现 Header,但请求时间超出了允许窗口。攻击者可能原样重放一条过去的合法请求,因此 Stripe、Slack 等流程还要检查时间戳。历史官方测试向量会故意显示“签名正确、时间过期”,测试算法时可以按样例时刻核对。
还不够。签名只证明当前请求与共享 Secret 匹配,服务端仍要验证事件类型、账号或店铺归属、对象状态和权限,并用事件 ID 做幂等。支付、发货、退款等操作尤其要防止同一合法事件被重复投递或并发处理。
普通字符串比较可能在发现第一个不同字符时提前返回,响应时间差异会泄露部分匹配信息。生产端应优先使用服务商官方 SDK,或使用 crypto.timingSafeEqual、hmac.compare_digest 等恒定时间比较,并先处理长度不一致。页面使用 Web Crypto 的 verify 完成字节比较。
不会上传。生成、解码和 SubtleCrypto.verify 都在当前浏览器执行;下载报告会省略 Secret 和正文内容,只保留字节数、算法、Header、结果与诊断。Header 本身仍可能关联真实事件,分享前应检查;生产 Secret 不应粘贴到普通网页。