Stripe Webhook签名验证失败:verifyWebhookSignature返回false
Stripe Webhook签名验证失败:crypto.timingSafeEqual始终返回false的排查方案
以下是核心排查方向和修复建议,结合你的代码逐一分析:
1. 原始请求体(Payload)被修改
Stripe签名验证依赖完全未修改的原始请求体字节流,任何中间件的自动解析、格式化、转码都会导致签名不匹配:
- 确认
ctx.request.body[unparsed]是原始的UTF-8编码字符串,而非Buffer或已解析的JSON对象。如果是Buffer,需先转成UTF-8字符串:const rawPayload = ctx.request.body[unparsed].toString('utf8'); - 对比Stripe Dashboard「Events」中该Webhook事件的原始Payload,和你代码中打印的
signedPayload里的payload部分,确保完全一致(包括空格、换行、引号格式等)。比如框架自动给JSON键值对添加空格({"a":1}变成{"a": 1}),会直接导致签名失效。
2. 签名头(stripe-signature)解析错误
你的签名头分割逻辑可能忽略了元素前后的空格:
Stripe签名头格式类似t=1699999999, v0=abcdef...,split(',')后第二个元素会带前导空格,导致key变成' v0'而非'v0',最终无法正确提取签名值。修改解析逻辑:
for (const element of elements) { const trimmedElement = element.trim(); // 去掉前后空格 const [key, value] = trimmedElement.split('='); if (key === 't') { timestamp = parseInt(value, 10); } else if (key.startsWith('v0')) { signatures['v0'] = value; } }
3. 时间戳与Payload拼接问题
确保拼接signedPayload时没有额外字符:
- 确认
timestamp是整数转成的字符串,无前置零或格式错误。 - 拼接时直接用
${timestamp}.${payload},不要添加任何空格或换行符。
4. Endpoint Secret正确性验证
再次确认:
- 使用的是Stripe Dashboard中对应Webhook端点的Signing Secret(不是API Key)。
- 测试环境和生产环境的Secret完全不同,不要混用。
5. Buffer转换与长度检查
crypto.timingSafeEqual要求两个Buffer长度完全一致,否则会返回false。在对比前添加长度检查:
const expectedBuf = Buffer.from(expectedSignature, 'hex'); const actualBuf = Buffer.from(signatures['v0'], 'hex'); if (expectedBuf.length !== actualBuf.length) { console.error('Signature length mismatch'); return false; } return crypto.timingSafeEqual(expectedBuf, actualBuf);
内容的提问来源于stack exchange,提问作者ilikechocolate
相关产品推荐
相关产品推荐

