NodeJS/Express后端Xero Webhook验证失败求助
Xero Webhook 签名验证失败问题解决
问题核心原因
你的代码存在两个关键问题导致签名不匹配:
- 请求体编码转换错误:使用
req.body.toString()将Buffer转为字符串时,可能因默认UTF-8编码与Xero计算签名时的原始字节流不一致,导致签名计算偏差。 - 中间件顺序与作用域问题:全局
express.json()中间件可能与/webhooks路由的express.raw()中间件产生潜在冲突,且先解析JSON再验证签名的流程不符合安全规范。
修正后的代码
import * as crypto from "crypto"; import express from "express"; import { Request, Response } from "express"; const app = express(); // 全局JSON解析中间件放在前面,不影响/webhooks路由的raw解析 app.use(express.json()); function verifyWebhookEventSignature(req: Request) { const webhookKey = process.env.XERO_WEBHOOK_KEY; if (!webhookKey) { console.error("XERO_WEBHOOK_KEY 环境变量未设置"); return false; } // 直接使用原始Buffer计算签名,避免编码转换误差 const computedSignature = crypto .createHmac("sha256", webhookKey) .update(req.body) .digest("base64"); const xeroSignature = req.headers["x-xero-signature"] as string; const isMatch = computedSignature === xeroSignature; if (isMatch) { console.log("签名验证通过!请求来自Xero"); } else { console.log("签名验证失败:请求可能非Xero发送或配置错误"); console.log(`计算签名:{${computedSignature}},预期签名:{${xeroSignature}}`); } return isMatch; } // 仅为/webhooks路由绑定raw中间件,确保请求体以原始Buffer形式接收 app.post("/webhooks", express.raw({ type: "application/json" }), async (req: Request, res: Response) => { // 优先验证签名,再处理请求体 const isValid = verifyWebhookEventSignature(req); if (isValid) { try { const payload = JSON.parse(req.body.toString()); console.log("收到Webhook事件:", req.headers, payload); res.status(200).send(); } catch (err) { console.error("解析Webhook payload失败:", err); res.status(400).send(); } } else { res.status(401).send(); } }); app.listen(5000, () => { console.log(`服务器运行在端口 ${5000}`); });
额外排查步骤
- 确认Webhook密钥正确性:检查
XERO_WEBHOOK_KEY环境变量是否无多余空格、换行,确保与Xero后台生成的密钥完全一致。 - 检查请求体完整性:确认ngrok未修改请求体(如自动解压gzip),Xero发送的原始请求体需完整传递到你的服务。
- 重新创建Webhook:删除Xero后台现有Webhook,重新生成密钥并配置,避免旧配置缓存问题。
内容的提问来源于stack exchange,提问作者david
相关产品推荐
相关产品推荐

