使用LemonSqueezy Webhooks时签名验证失败问题求助
问题
在LemonSqueezy API中,支付成功后Webhooks会发送payload,但签名验证始终失败。参考官方Webhook签名验证文档编写了TypeScript代码,具体代码如下:
import express, { Request, Response } from 'express'; import * as crypto from 'crypto'; import bodyParser from 'body-parser'; const webhook = express.Router(); let secret = "secretkey" webhook.use(bodyParser.json()) webhook.post('/webhooks', async (req: Request, res: Response) => { try { if (!req.body) { throw new Error('Missing request body'); } // Create HMAC signature const hmac = crypto.createHmac('sha256', secret); const digest = Buffer.from(hmac.update(JSON.stringify(req.body)).digest('hex'), 'utf8'); // Retrieve and validate X-Signature header const signature = Buffer.from(req.get('X-Signature') || '', 'utf8'); if (!crypto.timingSafeEqual(digest, signature)) { throw new Error('Invalid signature'); } const payload = req.body; console.log('Received valid webhook:', payload); return res.status(200).send({'Webhook received': payload}); } catch (err) { console.error(err); return res.status(400).send(err); } }); export default webhook
签名验证失败的常见原因及修复方案
- JSON序列化格式不匹配:
JSON.stringify(req.body)的输出可能和LemonSqueezy发送的原始请求体格式不一致(比如空格、键的顺序、数值格式等)。Express的body-parser.json()会解析请求体并重新构建JS对象,再用JSON.stringify序列化时,会和原始字节流产生差异。
修复:直接读取原始请求体字节,不要用解析后的req.body。改用bodyParser.raw({ type: 'application/json' }),再手动解析JSON:webhook.use(bodyParser.raw({ type: 'application/json' })) webhook.post('/webhooks', async (req: Request, res: Response) => { try { const rawBody = req.body.toString('utf8'); const payload = JSON.parse(rawBody); // 用原始字节生成签名 const hmac = crypto.createHmac('sha256', secret); const digest = Buffer.from(hmac.update(rawBody).digest('hex'), 'utf8'); // 后续验证逻辑不变 const signature = Buffer.from(req.get('X-Signature') || '', 'utf8'); if (!crypto.timingSafeEqual(digest, signature)) { throw new Error('Invalid signature'); } console.log('Received valid webhook:', payload); return res.status(200).send({'Webhook received': payload}); } catch (err) { console.error(err); return res.status(400).send(err); } }) - 密钥不匹配:确认代码中的
secret和LemonSqueezy后台设置的Webhook签名密钥完全一致,注意不要有多余的空格、换行符,区分大小写。 - 请求体被中间件篡改:如果有其他中间件在
body-parser之前修改了请求体内容,会导致原始签名验证失败,检查是否有额外的请求处理中间件。 - 代理服务器修改请求:若部署环境有Nginx等代理服务器,确保其没有对请求体进行编码转换或内容修改,保持原始请求体的完整性。
内容的提问来源于stack exchange,提问作者Deus
相关产品推荐
相关产品推荐

