You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 01:26:12