WhatsApp Cloud API:Webhook签名验证时而成功时而失败
Facebook Webhook签名验证时而成功时而失败的原因及修复
核心问题
你的验证逻辑存在致命问题:你用JSON.stringify(requestContext?.body)生成计算哈希的内容,但Facebook的签名是基于原始请求体的字节流计算的,不是框架解析后再序列化的JSON字符串。
当web框架把请求体解析成JSON对象后,再用JSON.stringify转成字符串,会出现多种导致哈希不匹配的情况:
- 原始请求的空格、换行符被修改
- JSON对象的键顺序可能和原始请求不一致
- 字符串的引号转义方式可能不同
这些差异会让你计算出的哈希和Facebook发送的签名不一致,进而导致验证时而成功时而失败(取决于序列化后的字符串是否碰巧和原始请求一致)。
修复方案
直接使用原始请求体的原始数据(未经过框架解析的字节或字符串)计算哈希,而非解析后的JSON对象再序列化。
修正后的代码示例
假设你的框架允许获取原始请求体(比如Express中配置express.raw()中间件,NestJS中用@RawBody()装饰器):
static createSha256Hash(data: Buffer | string, key: string): string { if (!data || (typeof data === 'string' && data.length === 0)) { throw new ValidationException(); } const sha256Hasher = crypto.createHmac('sha256', key); return sha256Hasher.update(data).digest('hex'); }
public validateWebhookSignature(requestContext: RequestContextModel): void { const signature = requestContext?.headers?.['X-Hub-Signature-256']?.replace('sha256=', ''); // 直接使用原始请求体,而非解析后的body再序列化 const rawBody = requestContext.rawBody; // 假设requestContext包含原始请求体的Buffer或字符串 if (!signature || !rawBody) { throw new ValidationException(); } const hash = CryptoUtil.createSha256Hash(rawBody, this._appSecret); if (signature !== hash) { throw new UnauthorizedException(); } }
额外注意事项
- 确保web框架没有自动修改原始请求体,比如不要启用压缩、自动转码等会改变原始字节的配置。
- 建议用
crypto.timingSafeEqual比较哈希值,避免时序攻击(虽非当前直接问题,但属于最佳实践):const bufferSignature = Buffer.from(signature, 'hex'); const bufferHash = Buffer.from(hash, 'hex'); if (bufferSignature.length !== bufferHash.length || !crypto.timingSafeEqual(bufferSignature, bufferHash)) { throw new UnauthorizedException(); }
内容的提问来源于stack exchange,提问作者Maor agai
相关产品推荐
相关产品推荐

