如何在NodeJS(Fastify)中验证WhatsApp API的X-Hub-Signature-256?
问题分析与解决方案
核心问题
你遇到的签名验证失败,根源在于你使用了解析后再序列化的请求体来计算哈希,而WhatsApp的签名是基于原始请求的JSON字节流生成的。Fastify默认会将JSON请求体解析为JavaScript对象,这个过程中会丢失原始JSON的格式细节(比如键的顺序、空格、转义方式),尤其是媒体消息(音频/图片)的结构更复杂,解析再序列化后的结果和原始请求体差异更大,导致哈希不匹配。
解决方案
直接使用原始请求体的字符串来计算签名,而不是解析后的request.body对象序列化结果。
步骤1:开启Fastify的原始请求体支持
在创建Fastify实例时,开启rawBody选项,让框架保留原始请求体:
import fastify from 'fastify'; const app = fastify({ rawBody: true, // 开启原始请求体保留 });
步骤2:修改路由中的签名验证逻辑
使用request.rawBody获取原始请求体字符串,用它来计算签名,之后再解析为对象处理业务逻辑:
app.post( "/", async ( request: FastifyRequest<{ Body: WhatsappReceivedMessage; }>, reply ) => { const xHubSignature = request.headers["x-hub-signature-256"] as string; // 获取原始请求体字符串 const rawBodyStr = request.rawBody.toString('utf-8'); if (!xHubSignature) { console.log("Missing X-Hub-Signature-256 header"); return reply.status(403).send("Forbidden"); } // 用原始字符串计算签名 const expectedSignature = generateWPPSignature( rawBodyStr, env.WPP_SECRET ); if (!validateWPPSignature(expectedSignature, xHubSignature)) { console.warn("Invalid X-Hub-Signature-256"); return reply.status(403).send("Forbidden"); } // 解析原始字符串为业务对象 const body = JSON.parse(rawBodyStr) as WhatsappReceivedMessage; const message = body.entry[0].changes[0].value.messages[0]; // 后续业务逻辑(发送消息、标记已读)保持不变... return reply.send("OK"); } );
步骤3:验证函数无需修改
你的generateWPPSignature和validateWPPSignature函数逻辑是正确的,不需要改动,只要传入原始请求体字符串即可。
关键说明
- WhatsApp计算签名时,使用的是发送给你的原始JSON请求的字节内容,任何对原始内容的修改(包括解析再序列化、调整空格/键顺序)都会导致哈希不一致。
- 确保
rawBody的编码是utf-8,和WhatsApp发送的请求编码一致,避免字符转义问题。
内容的提问来源于stack exchange,提问作者Borgim
相关产品推荐
相关产品推荐

