Shopify Webhook HMAC验证失败,求排查代码实现问题
Shopify Webhook HMAC验证始终失败的排查与解决
问题描述
我正在开发一款Shopify应用,尝试通过下述函数验证app_subscriptions/update主题Webhook的HMAC签名,但无论用===还是timingSafeEqual对比,函数始终返回false,生成的Hash和请求头里的HMAC值始终不匹配。代码如下:
async function validateHmac(req: Request) { let shopifyApiSecret = config.shopifyAppSecret; let hmac: any = req.headers["x-shopify-hmac-sha256"]; const message = JSON.stringify(req.body); const generatedHash = crypto .createHmac("sha256", shopifyApiSecret) .update(message) .digest("base64"); console.log({ message, generatedHash, hmac }); const signatureOk = crypto.timingSafeEqual( Buffer.from(generatedHash), Buffer.from(hmac) ); return signatureOk; }
核心问题分析及解决方案
错误核心:使用解析后的JSON对象重新序列化计算HMAC
Shopify计算HMAC签名时,用的是Webhook请求发送的原始HTTP请求体字节流,而不是你通过框架解析后得到的req.body再用JSON.stringify()序列化的结果。框架(比如Express、Next.js)在解析req.body时,会自动处理空格、键的排序、转义字符等,导致JSON.stringify(req.body)的结果和Shopify发送的原始请求体完全不一致,自然计算出的HMAC无法匹配。正确的处理方式
- 获取原始未修改的请求体:在解析
req.body之前,先读取并保存原始的请求体文本。例如:- 在Express中,需要使用
express.raw({ type: 'application/json' })中间件直接获取原始Buffer; - 在Next.js App Router中,用
await req.text()获取原始文本,再手动解析为JSON:const rawBody = await req.text(); const body = JSON.parse(rawBody);
- 在Express中,需要使用
- 用原始请求体计算HMAC:用获取到的原始请求体(字符串或Buffer)作为
update()方法的参数,而不是JSON.stringify(req.body):const generatedHash = crypto .createHmac("sha256", shopifyApiSecret) .update(rawBody) // 这里用原始请求体 .digest("base64");
- 获取原始未修改的请求体:在解析
额外注意事项
- 确保
shopifyApiSecret确实是应用的Client Secret(不是API Key); - 使用
timingSafeEqual时,要确保两个Buffer的长度一致,原始HMAC需用Buffer.from(hmac, 'base64')转换,避免编码问题; - 不要对原始请求体做任何修改(比如去除空格、格式化JSON),保持和Shopify发送的完全一致。
- 确保
内容的提问来源于stack exchange,提问作者D.K. Goutham Kishore
相关产品推荐
相关产品推荐

