← 返回工具箱

WEBHOOK / HMAC / RAW BODY

Webhook 签名生成与验签

按服务商真实规则复现签名,区分签名字节、Header 格式和时间窗口,定位“本地算得一样、服务端却验不过”的问题。

只放测试 Secret:所有计算都在浏览器本地完成,但生产 Signing Secret 不应粘贴到网页、聊天、截图或工单。线上服务还必须保留原始 Body、使用 timing-safe 比较并做好事件幂等。

已载入 GitHub 官方文档的已知签名样例,可直接点击验签。

X-Hub-Signature-256

1. 原始请求与 Secret

这里的换行、空格、转义和键顺序都会参与签名。请粘贴框架解析 JSON 之前读到的原始正文,不要格式化后再验。

PROVIDER RULES

X-Hub-Signature-256

HMAC-SHA256(raw_body),Hex 前缀 sha256=

  • 对未改动的 UTF-8 Body 计算 HMAC-SHA256。
  • Header 值必须带 sha256= 前缀。
  • 不要再使用旧的 X-Hub-Signature / SHA-1。

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));
上线前仍要做:在路由最前面保留原始请求字节;验证时间戳以阻止重放;使用服务商官方 SDK 或底层 timing-safe 比较;以事件 ID 做幂等;验签失败立即拒绝且不要记录 Secret。

RELATED TOOLS

查看全部工具 →

USE & REVIEW

Webhook 签名生成与验签常见问题

查看全部工具指南 →

为什么 Webhook 验签必须使用原始 Request Body?

服务商签名的是收到时那串确定的字节。先把 JSON 解析成对象再 stringify,可能改变空格、转义、字段顺序和结尾换行,哪怕内容语义相同,HMAC 也会完全不同。服务端应在 Body Parser 改写之前保存 raw body,再按对应服务商规则验签。

Stripe、GitHub、Shopify 和 Slack 的签名为什么不能用同一种格式?

它们虽然常用 HMAC-SHA256,但签名原文和输出格式不同。GitHub 对原始 Body 计算 Hex 并加 sha256=;Shopify 输出 Base64;Stripe 在 Body 前拼时间戳和句点;Slack 拼成 v0:timestamp:raw_body。算法名称相同不代表协议可以互换。

签名字节匹配,但页面提示时间戳过期是什么意思?

这说明 Secret、正文和签名算法能复现 Header,但请求时间超出了允许窗口。攻击者可能原样重放一条过去的合法请求,因此 Stripe、Slack 等流程还要检查时间戳。历史官方测试向量会故意显示“签名正确、时间过期”,测试算法时可以按样例时刻核对。

签名通过以后是否可以立即执行业务操作?

还不够。签名只证明当前请求与共享 Secret 匹配,服务端仍要验证事件类型、账号或店铺归属、对象状态和权限,并用事件 ID 做幂等。支付、发货、退款等操作尤其要防止同一合法事件被重复投递或并发处理。

生产代码为什么要用 timing-safe 比较?

普通字符串比较可能在发现第一个不同字符时提前返回,响应时间差异会泄露部分匹配信息。生产端应优先使用服务商官方 SDK,或使用 crypto.timingSafeEqual、hmac.compare_digest 等恒定时间比较,并先处理长度不一致。页面使用 Web Crypto 的 verify 完成字节比较。

Secret、原始 Body 和签名会上传或写进下载报告吗?

不会上传。生成、解码和 SubtleCrypto.verify 都在当前浏览器执行;下载报告会省略 Secret 和正文内容,只保留字节数、算法、Header、结果与诊断。Header 本身仍可能关联真实事件,分享前应检查;生产 Secret 不应粘贴到普通网页。