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

Node.js实现Mandrill webhook签名校验部分事件不匹配问题

Mandrill webhook 身份认证-签名校验
  • 运行环境:Node.js

问题表现

现有签名校验逻辑仅在处理send、reject类型事件时可正常匹配签名,处理open、click等其他类型事件时,会出现authentication-signature not matched(签名不匹配)异常。
原有问题代码如下:

function generateSignature(webhook_key, url, params) {
  var signed_data = url;
  const param_keys = Object.keys(params);
  param_keys.sort();
  param_keys.forEach(function (key) {
      signed_data += key + params[key];
  }); 

  hmac = crypto.createHmac('sha1', webhook_key);
  hmac.update(signed_data);

  return hmac.digest('base64');
}

let url = "https://your-app-domain.com/default/MandrillXP-new";
let key = "abcd1234"; //your mandrill webhook api key

let bodyPayload;
if(event.isBase64Encoded){
  bodyPayload = Buffer.from(event.body, 'base64').toString()
}else{
  bodyPayload = event.body
} 
  let splitData = req.body.split("=")
  let decodeData = decodeURIComponent(splitData[1]);    

var generatedSignature = generateSignature(key, url, { "mandrill_events": decodeData })

if (req.headers["x-mandrill-signature"]!== generatedSignature) {
  console.log("signature mismatch")
}else{
  console.log("signature  matched")
}

问题原因

  1. 手动用split("=")切分请求体的逻辑完全不可靠:mandrill_events是JSON格式的事件数组,open、click类事件会携带完整的跳转链接,链接本身就包含大量=字符,切分后仅取索引为1的片段会直接截断参数值,导致参与签名计算的内容和Mandrill端不一致。
  2. 变量混用:代码中同时出现了event.body和req.body两个请求体变量,在不同运行环境(如云函数、传统Node服务)下很容易取到错误的请求内容。
  3. 额外的解码逻辑破坏原始参数内容:手动调用decodeURIComponent处理切分后的片段,会改变原始参数的字符串内容,进一步导致签名计算偏差。

修复后的正确实现

使用Node.js内置的querystring模块解析form格式的请求体,避免手动切分的问题,同时统一请求体读取逻辑,去掉多余的解码操作:

const crypto = require('crypto');
const qs = require('querystring');

function generateSignature(webhookKey, url, params) {
  let signedData = url;
  // 按参数key字典序排序
  const sortedKeys = Object.keys(params).sort();
  sortedKeys.forEach(key => {
    // querystring.parse会自动处理URL编码,直接拼接值即可
    signedData += key + params[key];
  });

  const hmac = crypto.createHmac('sha1', webhookKey);
  hmac.update(signedData);
  return hmac.digest('base64');
}

// 配置项 - 替换为实际业务配置
const WEBHOOK_URL = "https://your-app-domain.com/default/MandrillXP-new";
const WEBHOOK_KEY = "abcd1234"; // Mandrill后台对应webhook的API密钥

// 统一读取请求体,适配云函数Base64编码场景
let rawBody;
if (event.isBase64Encoded) {
  rawBody = Buffer.from(event.body, 'base64').toString('utf-8');
} else {
  rawBody = event.body; // 传统Node服务可替换为req.rawBody,确保拿到原始form格式字符串
}

// 用内置querystring模块解析form参数,自动处理=、&等特殊字符
const formParams = qs.parse(rawBody);

// 计算签名
const generatedSignature = generateSignature(WEBHOOK_KEY, WEBHOOK_URL, formParams);

// 签名比对
if (req.headers["x-mandrill-signature"] !== generatedSignature) {
  console.log("signature mismatch");
  // 此处可添加拒绝请求的逻辑
} else {
  console.log("signature matched");
  // 签名校验通过,处理后续业务逻辑
  const events = JSON.parse(formParams.mandrill_events);
}

注意:WEBHOOK_URL必须和Mandrill后台配置的webhook地址完全一致,包括末尾是否带斜杠、是否携带查询参数,任何字符差异都会导致签名计算失败。

内容的提问来源于stack exchange,提问作者sundar_147

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.28 11:09:12