验证 Webhook 签名
使用 Standard Webhooks 官方库验签,然后再处理 Open Connector 的投递内容。
Open Connector 使用 Standard Webhooks 为 Platform Webhook 和 Trigger Webhook Subscription 投递签名。收到请求后,先用对应目的地或订阅的签名密钥验签,再信任 JSON 内容。
安装验签库
在接收端项目中安装官方 JavaScript 和 TypeScript 库:
pnpm add standardwebhooks创建目的地或订阅时,请把 Open Connector 返回的 whsec_ 密钥保存到服务端密钥管理系统。Platform Webhook 创建和轮换时,新密钥只展示一次。不要把它写进浏览器代码、日志或仓库。
验证收到的请求
在 JSON 解析器修改请求体之前读取原始内容。把请求体和三个 webhook-* 请求头传给 Webhook.verify():
import { Webhook } from "standardwebhooks";
export async function verifyOpenConnectorWebhook(request: Request, secret: string): Promise<unknown> {
if (!secret.startsWith("whsec_")) {
throw new Error("Missing Open Connector webhook signing secret");
}
// Platform 密钥使用 Base64URL;standardwebhooks 需要标准 Base64。
const encoded = secret.slice("whsec_".length).replaceAll("-", "+").replaceAll("_", "/");
const verifier = new Webhook(`whsec_${encoded}`);
const rawBody = await request.text();
return verifier.verify(rawBody, {
"webhook-id": request.headers.get("webhook-id") ?? "",
"webhook-timestamp": request.headers.get("webhook-timestamp") ?? "",
"webhook-signature": request.headers.get("webhook-signature") ?? "",
});
}standardwebhooks 会检查签名,并拒绝与接收端时钟相差超过五分钟的时间戳。验签成功时返回解析后的 JSON,失败时抛错。验签失败应返回非 2xx 响应。使用事件字段前,还要验证事件的结构。
Base64URL 转换只改变密钥的文本编码,不改变密钥字节。Trigger Webhook Subscription 使用的标准 Base64 密钥不会因此改变。验签前不要解析再重新序列化请求体;即使只改变空格,也会导致签名不匹配。
处理重试与密钥轮换
把 webhook-id 与对应的业务操作一起持久化,避免重试重复执行。有效签名和近期时间戳不能代替持久化去重。
Platform Webhook 正常轮换密钥后,请求会在最长 24 小时的重叠期内同时携带新旧密钥的签名。只要任意一个 v1 签名与传入的密钥匹配,验签就会通过。把一次性返回的新密钥放入接收端密钥管理系统,在重叠期结束后移除旧密钥。紧急轮换会立即停止使用旧密钥签名。
验签使用签名密钥,而不是 Open Connector API Key。接收端无需调用 Open Connector 来验证签名。