签名常见错误
本页列出合约 API 中最常导致签名失败的错误,并给出每种错误的错误写法与正确写法。签名规则本身见签名生成。
💡 最快的定位方法
- 用测试向量运行你的签名代码,确认得到完全相同的
original和signature。- 打印失败请求的
original字符串,与实际发出的请求对照:path、query、body 以及validate-timestamp必须逐字符一致。
错误信息
| 响应 | 含义 | 排查 |
|---|---|---|
HTTP 403 {"code":"403","msg":"invalid signature"} | 服务器构造的待签名串与你的不一致 | 下文第 1–11 项 |
HTTP 403 ... The request time <t> exceeds the specified time <now> limit by <n> second | validate-timestamp 超出 recvwindow | 第 10 项 |
HTTP 400 appkey <key> not found | API key 不存在 | key 是否复制正确;沙盒与生产的 key 不同 |
HTTP 400 Absence of [appKey] in client request is prohibited | 缺少请求头 validate-appkey | 请求头名称及拼写 |
HTTP 400 Absence of [validate-timestamp] in client request is prohibited | 缺少请求头 validate-timestamp | 请求头名称及拼写 |
HTTP 400,返回 JSON 解析错误信息(例如 Expected BEGIN_OBJECT but was STRING) | 发送了请求头 validate-signversion | 第 1 项 |
HTTP 400 For input string: "..." | validate-timestamp 或 validate-recvwindow 不是数字 | 发送纯整数 |
HTTP 403 The client source IP address is not configured | 该 API key 绑定了 IP,而请求来自其他 IP | API key 的 IP 白名单 |
HTTP 403 builder code illegal | 该 API key 配置不正确 | 联系客服 |
HTTP 200 {"result":{"openapiDocs":...}} | 请求路径不存在 | 第 9 项 |
| HTTP 403,返回 HTML 页面而不是 JSON | 请求在到达 API 之前被拦截(CDN / 防火墙) | 发送 User-Agent 请求头;不要通过代理调用 |
1. 发送了 validate-signversion 请求头
不支持 validate-signversion。携带它的请求无论如何签名,都会返回 invalid signature 或 HTTP 400。
❌ validate-signversion: 2
✅ 不发送 validate-signversion 请求头;按实际发送的请求体原样签名(见「签名生成」)
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 排序 → 签名 {"orderSide":..,"symbol":..},发送 {"symbol":..,"orderSide":..}
❌ 请求体构造了两次 → 两次序列化的空格、key 顺序或数字格式可能不同
✅ 只序列化一次 → 用这个字符串签名 → 发送同一个字符串
服务器不会对请求体排序。key 的顺序任意,只要签名的字符串与发送的字符串完全一致即可。
4. 数字、精度与小数位
请求体和 query 都按文本参与签名,因此数字在待签名串和请求中的写法必须完全一致。
❌ 签名 "price":82000 发送 "price":82000.0 (整数与浮点格式不同)
❌ 签名 "origQty":0.3 发送 "origQty":0.30000000000000004 (浮点运算 0.1 + 0.2)
❌ 签名 "price":"82000.00" 发送 "price":"82000" (只对一侧做了舍入/格式化)
❌ query:签名 price=82000 发送 ?price=82000.0
✅ 价格和数量以字符串传递,例如 "price":"82000.00"、"origQty":"0.001",
并在所有地方使用同一个字符串
使用字符串还能精确保留你想要的小数位,避免订单到达交易所之前发生浮点舍入。
5. 表单请求体(application/x-www-form-urlencoded)
表单请求体按实际发送的原样签名:不排序,也不做 URL 解码。
发送的 body : clientOrderId=a%3Ab&symbol=btc_usdt&orderSide=BUY
❌ 签名 : orderSide=BUY&symbol=btc_usdt&clientOrderId=a:b (排序且解码了)
✅ 签名 : clientOrderId=a%3Ab&symbol=btc_usdt&orderSide=BUY (与请求体完全一致)
6. 空请求体
只要发送了请求体,它就是待签名串的一部分,即使是 {}。
发送的 body : {}
❌ original : validate-appkey=..&validate-timestamp=..#/az/future/...
✅ original : validate-appkey=..&validate-timestamp=..#/az/future/...#{}
7. 套用了现货的签名规则
合约没有 method 段,请求头部分也只有两项。
❌ ...#POST#/az/future/trade/v1/order/create#{...}
❌ validate-algorithms=HmacSHA256&validate-appkey=..&validate-recvwindow=5000&validate-timestamp=..#/az/future/...
✅ validate-appkey=..&validate-timestamp=..#/az/future/trade/v1/order/create#{...}
8. query 字符串问题
query 段由解码后的值构成,按参数名的 ASCII 顺序排序。
请求 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 段,也不要多加
#。
9. 路径问题
待签名串中的 path 必须是实际请求的路径。
❌ https://f-api.azverse.xyz/az/future/trade/v1/order/create (包含了 host)
❌ /future/trade/v1/order/create (缺少 /az 前缀——请求到达不了 API)
❌ 签名 /az/future/.../create 请求 /az/future/.../create/ (末尾斜杠不一致)
✅ /az/future/trade/v1/order/create
10. 时间戳问题
❌ validate-timestamp: 1791541985 (秒,10 位) → 403 The request time ... exceeds ...
❌ 时间戳生成了两次:请求头里一个值,待签名串里另一个值 → 403 invalid signature
❌ 本地时钟偏差超过 recvwindow
✅ 只用一个 13 位毫秒时间戳,请求头和待签名串都用它;
保持系统时钟同步(NTP);必要时调大 validate-recvwindow(大于 60000 的值按 60000 处理)
11. 密钥与输出格式
❌ 输出为 Base64 → 必须输出 hex(HmacSHA256 为 64 个字符)
❌ secretKey 末尾带空格或换行(从文件或控制台复制时带入)
❌ 把 secretKey 按 hex 解码成字节 → secretKey 按原字符串(UTF-8)直接使用
❌ 在生产环境使用沙盒 key,或使用了另一个 API key 的 secretKey
❌ 发送 validate-algorithms: HmacSHA512,却按 HMAC-SHA256 计算 → 请求头与算法必须一致
✅ hex( HMAC-SHA256( secretKey, original ) )