微信支付 APIv3 请求签名报 401/SIGN_ERROR:5 行签名串与 Authorization 头逐行排查
官方文档:
- 签名生成:https://pay.weixin.qq.com/doc/global/v3/zh/4012354988
- 请求参数带 Body 如何计算签名:https://pay.weixin.qq.com/doc/v3/merchant/4012365336
- 签名验签(排障与 SIGN_ERROR detail):https://pay.weixin.qq.com/doc/global/v3/zh/4012355060
- 签名验签工具:https://pay.weixin.qq.com/doc/v3/merchant/4012365352
- 合作伙伴排障:https://pay.weixin.qq.com/doc/v3/partner/4012365875
- 开发指引 / APP 调起支付:https://pay.weixin.qq.com/doc/global/v3/zh/4012354124
一、先分清方向:请求签名 5 行,回调验签 3 行
APIv3 所有接口交互都要对「接口请求」做 SHA256withRSA 签名,对「响应 / 回调」做验签。请求侧是自己签名发给微信,回调侧是验微信发来的签名。两者签名串格式不同:请求签名串 5 行,应答 / 回调验签串 3 行。拼错方向是最常见的低级错误。
二、请求签名串:5 行,每行以 \n 结尾(包括最后一行)
1 HTTP请求方法\n
2 URL\n
3 请求时间戳\n
4 请求随机串\n
5 请求报文主体\n
- 第 2 行 URL 要去掉域名,只保留
/v3/...路径;有查询参数时末尾要带?和查询串。例:/v3/pay/transactions/out-trade-no/Tencentwechatpay0000457?mchid=1900006891 - 第 3 行是秒级时间戳(格林威治 1970-01-01 起的总秒数)。微信会拒绝很久以前发起的请求,Authorization 里的 timestamp 与发起请求时间不得超过 5 分钟。
- 第 5 行:POST/PUT 用真实发送的 JSON 报文,且必须是一行;GET 时 body 为空,第五行是空行加换行,也就是随机串后出现两个
\n。
三、算签名
用商户 API 证书私钥 apiclient_key.pem 对签名串做 SHA256withRSA,结果 Base64 得到 signature。
命令行示例(GET 空 body):
$ echo -n -e "GET\n/v3/certificates\n1554208460\n593BEC0C930BF1AFEB40B4A08C8FB242\n" | openssl dgst -sha256 -sign apiclient_key.pem | openssl base64 -A
四、Authorization 头
Authorization: WECHATPAY2-SHA256-RSA2048 mchid="1900007291",nonce_str="593BEC0C930BF1AFEB40B4A08C8FB242",signature="...",timestamp="1554208460",serial_no="408B07E79B8269FEC3D5D3E6AB8ED163A6A380DB"
- 认证类型目前仅
WECHATPAY2-SHA256-RSA2048。 serial_no是商户 API 证书序列号(apiclient_cert.pem),不是平台证书序列号,别混用;mchid、apiclient_key.pem、apiclient_cert.pem序列号必须一一对应。nonce_str、timestamp必须与计算签名时用的值完全一致。
五、官方列出的常见签名失败原因
- 签名串最后一行没有附加换行符;GET 空 body 少了一个换行。
- 手工拼接的 URL 和实际请求发送的不一致,建议用 HTTP 库 / URL 对象取 URL。
- 签名和设置 Authorization 头时用了前后两个时间戳。
- 签名和设置 Authorization 头时用了前后两个不同的随机串。
- 签名和请求时用了前后两次序列化的 JSON 作为请求体。字段顺序、空格、中文编码不同都会导致签名不匹配。
- 请求方法必须大写
GET/POST/PUT,不能小写。 - URL 不带域名,是
/v3/certificates而不是https://api.mch.weixin.qq.com/v3/certificates;不要出现多余的//。 - 代码里
\n要真换行,不能是字面字符"\n"。
六、其他常见 400 / 401 报错
- 400:
Accept和User-Agent必须都设置,缺一不可。Content-Type: application/json,Accept: application/json;APIv3 很可能拒绝无 User-Agent 的请求。 - 401 Unauthorized / SIGN_ERROR:Authorization 值格式错误时会提示「请检查上送的 Authorization,目前仅支持 WECHATPAY2-SHA256-RSA2048」;timestamp 与发起请求时间不得超过 5 分钟。
SIGN_ERROR 的 detail 对比法
验签失败会在应答 detail 里给出 sign_information:method、url、truncated_sign_message(微信侧实际用的签名串,换行显示成 \n)、sign_message_length(签名串字节长度)。把这两个值和自己在程序里拼的对比,定位最快。官方示例:
{"code":"SIGN_ERROR","message":"错误的签名,验签失败","detail":{"field":"signature","issue":"sign not match","location":"authorization","sign_information":{"method":"GET","url":"/payscore/user-service-state?service_id=500001&appid=wx...&openid=...","truncated_sign_message":"GET\n/payscore/user-service-state?service_id=500001&appid=...&openid=...\n1559194069\n18a427e78d2344e1a71156a2690cc4d6\n\n","sign_message_length":157}}}
sign_message_length 会随 url 长度变化。注意 GET 的签名串末尾是两个 \n。
七、回调方向:验签串 3 行
回调 / 应答验签串是 3 行:
应答时间戳\n
应答随机串\n
应答报文主体\n
回调 HTTP 头有 Wechatpay-Timestamp、Wechatpay-Nonce、Wechatpay-Signature、Wechatpay-Serial 四个。某些代理 / CDN 会过滤这些扩展头,取不到就先原样打日志,确认四个头都在。
八、APP 调起支付签名:另一套,4 行
签名串 4 行:应用 id、时间戳、随机字符串、预支付交易会话 ID,用商户私钥 SHA256withRSA + Base64 得到 paySign。
$ echo -n -e "wx8888888888888888\n1414561699\n5K8264ILTKch16CQ2502SI8ZNMTM67VS\nWX1217752501201407033233368018\n" | openssl dgst -sha256 -sign apiclient_key.pem | openssl base64 -A
九、工具
官方提供签名 / 验签工具,可以模拟生成请求签名并校验,用来确认「算法没错,是参数不一致」。如果工具生成的签名正确但接口仍报错,就是实际请求里的证书、签名信息或 Body 与工具中参与签名的参数不一致。