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

带参数与签名的请求提交异常:签名无效错误排查求助

解决Binance期货API签名无效错误(Code -1022)

收到错误:

{"code":-1022,"msg":"Signature for this request is not valid."}

已确认API密钥/密钥正确,也验证过签名计算逻辑,但怀疑参数与签名的传递步骤有问题,代码如下:

#[tokio::main]
async fn main() -> Result<(), Box<dyn Error>> {
    let (symbol, side, trade_type, quantity, price, time_inforce, api_key, api_secret) = (
        "BTCUSDT",
        "BUY",
        "LIMIT",
        "0.1",
        "20000.0",
        "GTC",
        "my-key",
        "my-secret",
    );
    let client = reqwest::Client::new();
    let mut headers_map = reqwest::header::HeaderMap::new();
    headers_map.insert("Content-Type", "application/json".parse().unwrap());
    headers_map.insert("X-MBX-APIKEY", api_key.parse().unwrap());

    let mut param_map = std::collections::HashMap::new();
    param_map.insert("symbol", symbol);
    param_map.insert("side", side);
    param_map.insert("type", trade_type);
    param_map.insert("quantity", quantity);
    let current_timestamp = chrono::Utc::now().timestamp_millis().to_string();
    param_map.insert("timestamp", current_timestamp.as_str());
    if trade_type == "LIMIT" {
        param_map.insert("price", price);
        param_map.insert("timeInForce", time_inforce);
    }
    let mut query = String::new();
    for (key, value) in &param_map {
        query.push_str(&format!("{}={}&", key, value));
    }
    query.pop(); // remove trailing '&'

    let signature = {
        type HmacSha256 = hmac::Hmac<sha2::Sha256>;
        let mut mac = HmacSha256::new_from_slice(api_secret.as_bytes())
            .expect("HMAC can take key of any size");
        mac.update(query.as_bytes());
        hex::encode(mac.finalize().into_bytes())
        // format!("{:02x}", mac.finalize().into_bytes())
    };
    let res = client
        .post("https://fapi.binance.com/fapi/v1/order") // fapi/v1/order/test: for test orders
        .headers(headers_map)
        .body(query)
        .query(&[("timestamp", &current_timestamp), ("signature", &signature)])
        .send()
        .await?
        .text()
        .await?;

    Ok(())
}

问题分析与解决方法

  • 参数传递格式错误:Binance签名API要求POST请求使用application/x-www-form-urlencoded格式,而非JSON。同时当前代码把参数拆分到body和query中,导致实际发送的参数集合和计算签名时的不一致(重复了timestamp)。

  • 参数顺序问题:HashMap是无序的,Binance要求参数按ASCII升序排列后再生成签名,无序遍历会导致签名计算错误。

  • 签名传递方式错误:签名应该和所有其他参数一起放在form body里,不能分开在query中传递。

修正后的代码

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (symbol, side, trade_type, quantity, price, time_inforce, api_key, api_secret) = (
        "BTCUSDT",
        "BUY",
        "LIMIT",
        "0.1",
        "20000.0",
        "GTC",
        "my-key",
        "my-secret",
    );
    let client = reqwest::Client::new();
    let mut headers_map = reqwest::header::HeaderMap::new();
    // 改为Binance要求的表单编码格式
    headers_map.insert("Content-Type", "application/x-www-form-urlencoded".parse().unwrap());
    headers_map.insert("X-MBX-APIKEY", api_key.parse().unwrap());

    // 使用BTreeMap保证参数按ASCII升序排列
    let mut param_map = std::collections::BTreeMap::new();
    param_map.insert("symbol", symbol);
    param_map.insert("side", side);
    param_map.insert("type", trade_type);
    param_map.insert("quantity", quantity);
    let current_timestamp = chrono::Utc::now().timestamp_millis().to_string();
    param_map.insert("timestamp", &current_timestamp);
    if trade_type == "LIMIT" {
        param_map.insert("price", price);
        param_map.insert("timeInForce", time_inforce);
    }

    // 生成待签名的参数字符串(包含URL编码)
    let query = param_map
        .iter()
        .map(|(k, v)| format!("{}={}", urlencoding::encode(k), urlencoding::encode(v)))
        .collect::<Vec<_>>()
        .join("&");

    // 计算签名
    let signature = {
        type HmacSha256 = hmac::Hmac<sha2::Sha256>;
        let mut mac = HmacSha256::new_from_slice(api_secret.as_bytes())
            .expect("HMAC can take key of any size");
        mac.update(query.as_bytes());
        hex::encode(mac.finalize().into_bytes())
    };

    // 拼接最终的表单数据(包含签名)
    let final_form_data = format!("{}&signature={}", query, signature);

    let res = client
        .post("https://fapi.binance.com/fapi/v1/order") // 测试可改用fapi/v1/order/test
        .headers(headers_map)
        .body(final_form_data)
        .send()
        .await?
        .text()
        .await?;

    println!("{}", res);
    Ok(())
}

关键改动说明

  1. 替换HashMap为BTreeMap,确保参数按ASCII升序排列,符合Binance的签名要求。
  2. 将Content-Type改为application/x-www-form-urlencoded,匹配API的接收格式。
  3. 使用urlencoding对参数进行URL编码,避免特殊字符导致的签名错误。
  4. 把签名加入到表单数据中,统一通过body发送,不再拆分到query参数。
  5. 确保计算签名的参数字符串和实际发送的完全一致。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.24 04:17:14