SDK
Webhook 验签
跨运行时验证 Webhook 签名。
签名内容
每次投递携带三个 header:
X-Product-Signature: t=1780301011,v1=7b11b2c1…
X-Event-Id: evt_01JYA…
X-Delivery-Id: del_01JYA…签名为 HMAC-SHA256(secret, "${timestamp}.${rawBody}"),hex 编码。
5 分钟时间窗防重放;签名比较常量时间,避免计时侧信道。
在 handler 中验证
SDK 直接 re-export 验签函数,框架用户不需要额外依赖:
import { verifySignature, SIGNATURE_HEADER } from '@stableops/api-sdk/webhooks'
export async function POST(req: Request) {
// 必须用 *原始* body 验签。不要先 JSON.parse 再 stringify,
// 即便对同一对象,key 顺序也可能变化。
const rawBody = await req.text()
const result = verifySignature({
secrets: [process.env.STABLEOPS_WEBHOOK_SECRET!],
header: req.headers.get(SIGNATURE_HEADER.toLowerCase()) ?? undefined,
rawBody,
})
if (!result.ok) {
return new Response(JSON.stringify({ reason: result.reason }), {
status: 400,
})
}
const event = JSON.parse(rawBody)
// 业务逻辑,按 event.id 幂等
return new Response('ok')
}密钥轮换
轮换接口立即返回新 secret,并保留旧 secret 24 小时。期间把两条 secret 同时
传给 verifySignature:
verifySignature({
secrets: [
process.env.STABLEOPS_WEBHOOK_SECRET!,
process.env.STABLEOPS_WEBHOOK_SECRET_PREVIOUS!,
],
header,
rawBody,
})失败原因
verifySignature 返回判别联合,ok: false 时读取 reason:
| reason | 含义 |
|---|---|
missing_header | 缺失 X-Product-Signature |
invalid_format | header 不符合 t=…,v1=… |
timestamp_expired | 超出 5 分钟窗口 |
bad_signature | HMAC 与任一 secret 都不匹配 |
重复 delivery
验签通过后,按 X-Event-Id(推荐)或自家业务键去重。平台的重试调度 +
网络重试,意味着 同一事件可能落多次——handler 必须幂等。
Edge / Serverless
verifySignature 使用 node:crypto 的 createHmac、timingSafeEqual 和 Buffer。
主流 edge 运行时都通过原生或兼容层支持 node:crypto:Deno、Vercel 原生支持,
无需额外 polyfill;Cloudflare Workers 需在 wrangler.toml 中启用 nodejs_compat
兼容性标志后即可使用。