Webhook 签名验证
Webhook 签名验证
发送端实际协议
| Header | 内容 |
|---|---|
X-Webhook-Signature | HMAC-SHA256的64位小写十六进制摘要,无前缀 |
X-Webhook-Event | 事件名称,未被签名覆盖 |
Content-Type | application/json |
原始 signing_secret 是注册/轮换时一次返回的明文。实际key派生为 UTF8(SHA256(secret).hex_lowercase),签名消息是未经解析的原始HTTP body字节。官方SDK helper封装该派生与定时无关比较;不要将服务端存储hash作为原始secret传入。
签名验证body真实性与完整性。当前协议没有签名时间戳或投递ID,不提供防重放或事件header认证。不要依赖未经签名的header做授权决定;业务去重需要你自己的可信标识与存储,不能假设每个payload包含统一event ID。
使用正式SDK校验
下面是可独立运行的验签程序。为复现原始字节,将接收到的body原样base64存入 APPFORGES_WEBHOOK_BODY_BASE64,signature来自 X-Webhook-Signature,secret来自安全配置。真实HTTP handler也应先获取raw body、校验成功后才JSON解析;生产不要日志记录secret或完整body。
安装见快速开始。示例输出true/false,只展示验签,不处理业务事件。
TypeScript
import { verifyWebhookSignature } from "@appforges/sdk/runtime/webhook";
const body = new Uint8Array(Buffer.from(process.env.APPFORGES_WEBHOOK_BODY_BASE64!, "base64"));
const valid = await verifyWebhookSignature(
body,
process.env.APPFORGES_WEBHOOK_SIGNATURE!,
[process.env.APPFORGES_WEBHOOK_SECRET!],
);
console.log(valid);Python
import base64
import os
from appforges import verify_webhook_signature
body = base64.b64decode(os.environ["APPFORGES_WEBHOOK_BODY_BASE64"], validate=True)
valid = verify_webhook_signature(
body,
os.environ["APPFORGES_WEBHOOK_SIGNATURE"],
[os.environ["APPFORGES_WEBHOOK_SECRET"]],
)
print(valid)Go
package main
import (
"encoding/base64"
"fmt"
"log"
"os"
"github.com/appforges/appforges-sdk-go/runtime"
)
func main() {
body, err := base64.StdEncoding.DecodeString(os.Getenv("APPFORGES_WEBHOOK_BODY_BASE64"))
if err != nil { log.Fatal(err) }
valid := runtime.VerifyWebhookSignature(body,
os.Getenv("APPFORGES_WEBHOOK_SIGNATURE"),
[]string{os.Getenv("APPFORGES_WEBHOOK_SECRET")})
fmt.Println(valid)
}轮换与错误处理
轮换立即替换发送端密钥,没有24小时双签保证。消费者可短期配置多个原始secret,兼容在途旧请求;完成迁移后移除旧secret。错误key、空key、body篡改和畸形signature应失败。校验失败时停止业务处理,按你的HTTP接收契约返回错误。