签名常见错误
本页列出现货 API 中最常导致签名失败的错误,并给出每种错误的错误写法与正确写法。签名规则本身见签名生成。
💡 最快的定位方法
- 用测试向量运行你的签名代码,确认得到完全相同的
original和signature。- 打印失败请求的
original字符串,与实际发出的请求对照:method、path、query、body 以及所有validate-*请求头的值必须逐字符一致。
错误码
鉴权错误以 HTTP 200 和 "rc":1 返回,原因在 mc 中,例如 {"rc":1,"mc":"AUTH_103","ma":[],"result":null}。
| mc | 含义 | 排查 |
|---|---|---|
| AUTH_001 | 缺少请求头 validate-appkey | 请求头名称及拼写 |
| AUTH_002 | 缺少请求头 validate-timestamp | 请求头名称及拼写 |
| AUTH_003 | 缺少请求头 validate-recvwindow | 第 9 项 |
| AUTH_004 | validate-recvwindow 不在 5000 ~ 60000 之间 | 第 9 项 |
| AUTH_005 | 缺少请求头 validate-algorithms | 第 9 项 |
| AUTH_006 | validate-algorithms 不是受支持的算法名称 | 第 9 项 |
| AUTH_007 | 缺少请求头 validate-signature | 请求头名称及拼写 |
| AUTH_101 | API key 不存在 | key 是否复制正确;沙盒与生产的 key 不同 |
| AUTH_102 | API key 未激活 | API key 状态 |
| AUTH_103 | 签名错误——服务器构造的待签名串与你的不一致 | 下文第 1–10 项 |
| AUTH_104 | 请求来自未绑定到该 API key 的 IP | API key 的 IP 白名单 |
| AUTH_105 | validate-timestamp 超出 recvwindow | 第 8 项 |
| AUTH_106 | 该 API key 没有此接口的权限 | API key 权限(例如 TRADE、TRANSFER) |
For input string: "..." | validate-timestamp 或 validate-recvwindow 不是数字 | 发送纯整数 |
如果返回 HTTP 403 且内容是 HTML 页面而不是 JSON,说明请求在到达 API 之前就被拦截了(CDN / 防火墙):请发送 User-Agent 请求头,并且不要通过代理调用。
1. 套用了合约的签名规则
现货需要 method 段,请求头部分有四项。
❌ validate-appkey=..&validate-timestamp=..#POST#/az/spot/order#{...} (合约式的请求头部分)
❌ validate-algorithms=..&validate-appkey=..&validate-recvwindow=..&validate-timestamp=..#/az/spot/order#{...} (缺少 method)
❌ ...#post#/az/spot/order#{...} (method 为小写)
✅ validate-algorithms=HmacSHA256&validate-appkey=..&validate-recvwindow=5000&validate-timestamp=..#POST#/az/spot/order#{...}
2. HTTP 库重新序列化了请求体
你签名的是一个 JSON 字符串,但 HTTP 库发送的却是另一个。最常见的是 Python requests 使用 json= 参数:它会重新序列化,并在 , 和 : 后面加上空格。
# ❌ signed '{"symbol":"btc_usdt","price":"70000"}' but sent '{"symbol": "btc_usdt", "price": "70000"}'
body = json.dumps(payload, separators=(",", ":"))
sign(... + "#" + body)
requests.post(url, json=payload, headers=headers)
# ✅ send the exact string that was signed
requests.post(url, data=body.encode("utf-8"), headers=headers)
任何接收对象并自行序列化的客户端都存在同样问题(例如传入 POJO 或 Map 的 Java RestTemplate、WebClient):请只构造一次 JSON 字符串,并把这个字符串作为请求体发送。
3. 签名的内容不是实际发送的请求体
❌ 对 str(payload) 签名 → {'symbol': 'btc_usdt', 'price': None} (Python repr,不是 JSON)
❌ 只在签名时对 key 排序 → 签名 {"side":..,"symbol":..},发送 {"symbol":..,"side":..}
❌ 请求体构造了两次 → 两次序列化的空格、key 顺序或数字格式可能不同
✅ 只序列化一次 → 用这个字符串签名 → 发送同一个字符串
服务器不会对请求体排序。key 的顺序任意,只要签名的字符串与发送的字符串完全一致即可。
4. 数字、精度与小数位
请求体和 query 都按文本参与签名,因此数字在待签名串和请求中的写法必须完全一致。
❌ 签名 "price":82000 发送 "price":82000.0 (整数与浮点格式不同)
❌ 签名 "quantity":0.3 发送 "quantity":0.30000000000000004 (浮点运算 0.1 + 0.2)
❌ 签名 "price":"82000.00" 发送 "price":"82000" (只对一侧做了舍入/格式化)
❌ query:签名 price=82000 发送 ?price=82000.0
✅ 价格和数量以字符串传递,例如 "price":"82000.00"、"quantity":"0.001",
并在所有地方使用同一个字符串
使用字符串还能精确保留你想要的小数位,避免订单到达交易所之前发生浮点舍入。
5. 空请求体
只要发送了请求体,它就是待签名串的一部分,即使是 {}。
发送的 body : {}
❌ original : ...#POST#/az/spot/...
✅ original : ...#POST#/az/spot/...#{}
6. query 字符串问题
query 段由解码后的值构成,按参数名的 ASCII 顺序排序,并单独作为一个 # 段。
请求 URL : /az/spot/balance?currency=usdt
❌ ...#GET#/az/spot/balance?currency=usdt (query 直接拼在 path 后面)
✅ ...#GET#/az/spot/balance#currency=usdt
请求 URL : ...?symbol=btc_usdt&Limit=10&endTime=1700000000000
❌ symbol=btc_usdt&Limit=10&endTime=1700000000000 (未排序)
❌ endTime=1700000000000&Limit=10&symbol=btc_usdt (忽略大小写排序)
✅ Limit=10&endTime=1700000000000&symbol=btc_usdt (ASCII:大写字母排在小写字母之前)
请求 URL : ...?symbols=btc_usdt%2Ceth_usdt
❌ symbols=btc_usdt%2Ceth_usdt (编码后的值)
✅ symbols=btc_usdt,eth_usdt (解码后的值)
- URL 中的字面量
+会被解码为空格。请在 URL 中把+编码为%2B,签名时使用+。 - 同一个参数名出现多次时,只使用第一个值——请避免重复的参数名。
- 参数一律以
key=value形式发送;不带=的参数名(?flag)无法可靠签名。 - 没有查询参数 → 没有 query 段,也不要多加
#。
7. 路径问题
待签名串中的 path 必须是实际请求的路径。
❌ https://s-api.azverse.xyz/az/spot/order (包含了 host)
❌ 签名 /az/spot/order 请求 /az/spot/order/ (末尾斜杠不一致)
✅ /az/spot/order
8. 时间戳问题
❌ validate-timestamp: 1791541985 (秒,10 位) → AUTH_105
❌ 时间戳生成了两次:请求头里一个值,待签名串里另一个值 → AUTH_103
❌ 本地时钟偏差超过 recvwindow → AUTH_105
✅ 只用一个 13 位毫秒时间戳,请求头和待签名串都用它;
保持系统时钟同步(NTP);必要时调大 validate-recvwindow(最大 60000)
9. 必填请求头及其取值
❌ 缺少 validate-recvwindow → AUTH_003
❌ validate-recvwindow: 3000 或 70000 → AUTH_004(必须在 5000 – 60000 之间)
❌ 缺少 validate-algorithms → AUTH_005
❌ validate-algorithms: SHA256 → AUTH_006(应使用 HmacSHA256)
❌ 请求头写的是 HmacSHA512,代码按 HMAC-SHA256 计算 → AUTH_103
✅ 五个请求头全部发送,并把完全相同的值放入待签名串
10. 密钥与输出格式
❌ 输出为 Base64 → 必须输出 hex(HmacSHA256 为 64 个字符)
❌ secretKey 末尾带空格或换行(从文件或控制台复制时带入)
❌ 把 secretKey 按 hex 解码成字节 → secretKey 按原字符串(UTF-8)直接使用
❌ 在生产环境使用沙盒 key,或使用了另一个 API key 的 secretKey
✅ hex( HMAC-SHA256( secretKey, original ) )