Binance API Signature Mismatch: Complete Fix Guide
Everything you need to debug and fix Binance API signature mismatch errors — from HMAC authentication basics to timestamp sync and working code examples in Python and JS.
Everything you need to debug and fix Binance API signature mismatch errors — from HMAC authentication basics to timestamp sync and working code examples in Python and JS.
You set up your trading bot, double-checked your API keys, and hit run — then Binance throws back a cryptic -1022 error: "Signature for this request is not valid." If this sounds familiar, you're not alone. API signature mismatch is one of the most common roadblocks for traders building automated strategies on Binance. The good news: it's almost always fixable in under ten minutes once you understand what's actually being signed and why it goes wrong.
Binance uses HMAC-SHA256 to verify that requests come from you and haven't been tampered with in transit. Every request touching account data — orders, balances, trade history — requires a signature. The process: take all your request parameters, serialize them into a query string, then compute an HMAC-SHA256 hash using your API secret as the key. That hash gets appended to your request as the 'signature' parameter.
Your API key goes in the request header (X-MBX-APIKEY), while the signature goes in the query string or request body. Binance recomputes the signature server-side using the same parameters and your stored secret. If the two don't match, you get -1022. This means any difference — a single character, an extra space, a different parameter order — will cause authentication to fail.
import hmac
import hashlib
import time
import requests
from urllib.parse import urlencode
API_KEY = "your_api_key_here"
API_SECRET = "your_api_secret_here"
BASE_URL = "https://api.binance.com"
def create_signature(params: dict) -> str:
query_string = urlencode(params)
return hmac.new(
API_SECRET.encode("utf-8"),
query_string.encode("utf-8"),
hashlib.sha256
).hexdigest()
def get_account_info():
params = {
"timestamp": int(time.time() * 1000),
"recvWindow": 5000
}
params["signature"] = create_signature(params)
headers = {"X-MBX-APIKEY": API_KEY}
response = requests.get(
f"{BASE_URL}/api/v3/account",
params=params,
headers=headers
)
data = response.json()
if "code" in data:
print(f"API Error {data['code']}: {data['msg']}")
return None
return data
result = get_account_info()
print(result)
The root causes fall into a handful of categories. Once you know them, debugging becomes systematic rather than guesswork.
Binance error -1021 means your timestamp is outside the recvWindow tolerance. Error -1022 means the signature itself is wrong. These two errors have different root causes — don't confuse them when debugging.
Before touching any signature logic, always check your clock sync. This is the most overlooked cause of API authentication failures, especially on cloud servers and VMs. Binance's /api/v3/time endpoint is public and requires no authentication — it's the perfect diagnostic tool. Run this on whatever machine your bot actually runs on, not just your local dev machine.
import requests
import time
def check_time_sync():
server_data = requests.get(
"https://api.binance.com/api/v3/time"
).json()
server_time = server_data["serverTime"]
local_time = int(time.time() * 1000)
drift_ms = abs(server_time - local_time)
print(f"Server time : {server_time}")
print(f"Local time : {local_time}")
print(f"Drift : {drift_ms} ms")
if drift_ms > 1000:
print("WARNING: Clock drift exceeds 1000ms — signature requests will fail!")
print("Fix: run 'sudo ntpdate -s time.nist.gov' or enable NTP sync.")
elif drift_ms > 500:
print("CAUTION: Drift approaching unsafe range. Monitor closely.")
else:
print("Clock sync OK — within acceptable range.")
return drift_ms
drift = check_time_sync()
If your drift is over 1000ms, fix your system clock before investigating anything else. On Linux, run sudo ntpdate -s time.nist.gov or enable systemd-timesyncd. On Windows, resync through Date and Time settings. If you can't adjust the clock (some container environments restrict this), you can offset your timestamp generation by fetching server time before each request — but treat this as a last resort, since it adds latency on every authenticated call.
Node.js bots hit a common pitfall: URLSearchParams serializes parameters differently than Python's urlencode in certain edge cases, especially with arrays and nested objects. Keep your parameter values as flat strings and numbers, and you avoid most of these issues. Here's a production-ready implementation for Binance's /api/v3/openOrders endpoint that you can adapt to any signed endpoint:
const crypto = require('crypto');
const axios = require('axios');
const API_KEY = 'your_api_key_here';
const API_SECRET = 'your_api_secret_here';
const BASE_URL = 'https://api.binance.com';
function createSignature(params) {
const queryString = new URLSearchParams(params).toString();
return crypto
.createHmac('sha256', API_SECRET)
.update(queryString)
.digest('hex');
}
async function getOpenOrders(symbol = 'BTCUSDT') {
const params = {
symbol,
timestamp: Date.now(),
recvWindow: 5000,
};
params.signature = createSignature(params);
try {
const { data } = await axios.get(`${BASE_URL}/api/v3/openOrders`, {
params,
headers: { 'X-MBX-APIKEY': API_KEY },
});
return data;
} catch (err) {
const apiErr = err.response?.data;
// -1022 = invalid signature, -1021 = timestamp out of recvWindow
console.error('Binance API error:', apiErr || err.message);
throw err;
}
}
getOpenOrders('ETHUSDT')
.then(orders => console.log(`Open orders: ${orders.length}`))
.catch(console.error);
Notice that the signature is added to the params object after all other parameters are finalized. This is critical — if you modify any parameter after computing the signature, the signature is immediately invalidated. Build your complete parameter set first, sign it in one shot, then send the request.
One-off fixes are fine for getting started, but production trading bots need a more systematic approach. Signature failures at 3am while a position is open aren't just annoying — they can be expensive. Whether you're running strategies on Binance, KuCoin, or Bybit, these production principles apply across all exchanges.
If you're running automated strategies and want to validate signals before execution, platforms like VoiceOfChain provide real-time trading signals with exchange-specific entry points. Having a reliable signal layer means your bot only fires API calls when there's a genuine opportunity — reducing unnecessary requests and the blast radius of any authentication issues.
Exchanges like OKX and Gate.io use similar HMAC-based authentication but have different timestamp tolerances and endpoint structures. If you're running multi-exchange bots, abstract your authentication layer so each exchange gets its own signing implementation rather than sharing a generic function that might make wrong assumptions about parameter encoding or body construction.
Binance API signature mismatches almost always trace back to one of three things: clock drift, a wrong or malformed API secret, or incorrect parameter serialization. The systematic fix is: check your timestamp sync first, verify your secret has no hidden whitespace, then confirm your parameter string is being built and encoded correctly before signing. The Python and JavaScript examples in this guide give you working implementations you can drop into any project.
Once your authentication is solid, shift your focus to what your bot is actually doing with the API. Whether you're executing signals from VoiceOfChain or running your own technical analysis on Binance, KuCoin, or any other exchange, reliable API auth is the foundation everything else depends on. Get it right once, test it on a real endpoint, and it will run without issues indefinitely.