Transnational支付webhook POST请求签名验证失败问题求助
问题修复点
你的实现存在4处核心错误,修正后即可正常验证:
- 多余的Body编码操作:官方生成签名时直接使用JSON序列化后的原始字节流计算HMAC,没有做任何URL转义,你额外添加的
Uri.EscapeUriString、WebUtility.UrlEncode会导致输入HMAC的内容和官方完全不一致,直接删除这两步即可。 - 哈希值处理错误:官方直接对HMAC输出的32位原始哈希字节做Base64 URL编码,你的
GenerateSignature方法将字节转成了十六进制字符串,相当于把32字节转为64字符的文本后再编码,结果必然不匹配。 - 字符编码错误:Go的JSON序列化默认输出UTF-8字节流,你使用ASCII编码会导致非ASCII字符场景下字节流不匹配,需统一改为UTF-8编码。
- Base64解码适配问题:你遇到的解码报错是因为官方用的是无填充的Raw URL Base64,C#默认Base64解码要求长度为4的倍数,不足时需要补
=填充位。
正确实现代码
using System.Security.Cryptography; using System.Text; using System.Linq; // 你的签名密钥 var clientSecret = "[你的控制面板里的Signature UUID]"; // 原始请求Body(注意不要做任何修改、转义,直接读取HTTP请求的原始内容字节) var rawBody = "[Webhook请求的原始JSON Body]"; // 请求头里的Signature值 var headerSignature = "RwtK6Kr4NOOFs9mjYPpEJWxSJQr23k6uXDktj4QEjGk"; // 1. 本地计算HMACSHA256原始哈希字节 byte[] rawHash; using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(clientSecret))) { rawHash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody)); } // 2. 方式一:解码请求头签名为原始字节,直接比对字节(推荐,性能更高) var decodedHeaderSig = Base64UrlDecode(headerSignature); bool isMatch = decodedHeaderSig.SequenceEqual(rawHash); // 方式二:将本地哈希编码为Base64 URL字符串,和请求头比对 // var localSignature = Base64UrlEncode(rawHash); // bool isMatch = localSignature == headerSignature; Console.WriteLine(isMatch ? "Matches" : "Does not match"); // Base64 URL编码(和你原有实现一致,可复用) static string Base64UrlEncode(byte[] arg) { var s = Convert.ToBase64String(arg); s = s.Split('=')[0]; s = s.Replace('+', '-'); s = s.Replace('/', '_'); return s; } // 新增Base64 URL解码方法,解决你之前的解码报错问题 static byte[] Base64UrlDecode(string input) { input = input.Replace('-', '+').Replace('_', '/'); // 补全填充位 switch (input.Length % 4) { case 2: input += "=="; break; case 3: input += "="; break; } return Convert.FromBase64String(input); }
注意事项
读取请求Body时一定要拿原始未修改的字节流,不要用框架自动反序列化后的对象再序列化成JSON,不同序列化库的转义规则、换行符差异可能会导致字节流不匹配。
内容的提问来源于stack exchange,提问作者Larry
相关产品推荐
相关产品推荐

