-
-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Open
Labels
needs-infoIssue is lacking sufficient information and will be closed if not providedIssue is lacking sufficient information and will be closed if not provided
Description
Describe the bug
Summary
The Binance API signature authentication works correctly when using the requests
library but fails with "signature not work fine for binance" error when using aiohttp
. Both functions use identical signature generation logic, but the HTTP request handling differs between the two libraries.
Environment
- Python version: 3.11+
- Libraries:
requests
,aiohttp
,hmac
,hashlib
- API: Binance Futures Testnet (
https://testnet.binancefuture.com
)
Expected Behavior
Both send_signed_request()
(using requests) and async_send_signed_request()
(using aiohttp) should successfully authenticate with Binance API using the same signature generation method.
Code Comparison
Working Version (Requests)
def send_signed_request(http_method, url_path, payload={}):
query_string = urlencode(payload)
query_string = query_string.replace("%27", "%22")
if query_string:
query_string = "{}×tamp={}".format(query_string, get_timestamp())
else:
query_string = "timestamp={}".format(get_timestamp())
url = (
BASE_URL + url_path + "?" + query_string + "&signature=" + hashing(query_string)
)
params = {"url": url, "params": {}}
response = dispatch_request(http_method)(**params)
return response.json()
Failing Version (Aiohttp)
async def async_send_signed_request(http_method, url_path, payload={}):
query_string = urlencode(payload)
query_string = query_string.replace("%27", "%22")
if query_string:
query_string = "{}×tamp={}".format(query_string, get_timestamp())
else:
query_string = "timestamp={}".format(get_timestamp())
url = (
BASE_URL + url_path + "?" + query_string + "&signature=" + hashing(query_string)
)
params = {"url": url, "params": {}}
async with aiohttp.ClientSession(headers={"Content-Type": "application/json;charset=utf-8", "X-MBX-APIKEY": KEY}) as session:
async with session.request(http_method, **params) as response:
return await response.json()
Actual Behavior
- ✅
send_signed_request()
with requests: Works correctly - ❌
async_send_signed_request()
with aiohttp: Returns signature error from Binance
To Reproduce
Here is the full code:
import hmac
import time
import hashlib
import requests
import aiohttp
import asyncio
from urllib.parse import urlencode
KEY = ""
SECRET = ""
BASE_URL = "https://testnet.binancefuture.com" # testnet base url
def hashing(query_string):
return hmac.new(
SECRET.encode("utf-8"), query_string.encode("utf-8"), hashlib.sha256
).hexdigest()
def get_timestamp():
return int(time.time() * 1000)
def dispatch_request(http_method):
session = requests.Session()
session.headers.update(
{"Content-Type": "application/json;charset=utf-8", "X-MBX-APIKEY": KEY}
)
return {
"GET": session.get,
"DELETE": session.delete,
"PUT": session.put,
"POST": session.post,
}.get(http_method, "GET")
# used for sending request requires the signature
def send_signed_request(http_method, url_path, payload={}):
query_string = urlencode(payload)
# replace single quote to double quote
query_string = query_string.replace("%27", "%22")
if query_string:
query_string = "{}×tamp={}".format(query_string, get_timestamp())
else:
query_string = "timestamp={}".format(get_timestamp())
url = (
BASE_URL + url_path + "?" + query_string + "&signature=" + hashing(query_string)
)
print("{} {}".format(http_method, url))
params = {"url": url, "params": {}}
response = dispatch_request(http_method)(**params)
return response.json()
async def async_send_signed_request(http_method, url_path, payload={}):
query_string = urlencode(payload)
# replace single quote to double quote
query_string = query_string.replace("%27", "%22")
if query_string:
query_string = "{}×tamp={}".format(query_string, get_timestamp())
else:
query_string = "timestamp={}".format(get_timestamp())
url = (
BASE_URL + url_path + "?" + query_string + "&signature=" + hashing(query_string)
)
print("{} {}".format(http_method, url))
params = {"url": url, "params": {}}
async with aiohttp.ClientSession(headers={"Content-Type": "application/json;charset=utf-8", "X-MBX-APIKEY": KEY}) as session:
async with session.request(http_method, **params) as response:
return await response.json()
# used for sending public data request
def send_public_request(url_path, payload={}):
query_string = urlencode(payload, True)
url = BASE_URL + url_path
if query_string:
url = url + "?" + query_string
print("{}".format(url))
response = dispatch_request("GET")(url=url)
return response.json()
params = {
"batchOrders": [
{
"symbol": "BTCUSDT",
"side": "BUY",
"quantity": "0.01",
"type": "LIMIT",
"price": "110575.1",
"timeInForce": "GTC",
},
{
"symbol": "BTCUSDT",
"side": "BUY",
"quantity": "0.01",
"type": "LIMIT",
"price": "110464.0",
"timeInForce": "GTC",
},
]
}
response = send_signed_request("POST", "/fapi/v1/batchOrders", params)
print(response)
async def main():
response = await async_send_signed_request("POST", "/fapi/v1/batchOrders", params)
print(response)
if __name__ == "__main__":
asyncio.run(main())
You can get the apiKey and secret from Binance Testnet

Expected behavior
You would get
## requests
POST https://testnet.binancefuture.com/fapi/v1/batchOrders?batchOrders=%5B%7B%22symbol%22%3A+%22BTCUSDT%22%2C+%22side%22%3A+%22BUY%22%2C+%22quantity%22%3A+%220.01%22%2C+%22type%22%3A+%22LIMIT%22%2C+%22price%22%3A+%22110575.1%22%2C+%22timeInForce%22%3A+%22GTC%22%7D%2C+%7B%22symbol%22%3A+%22BTCUSDT%22%2C+%22side%22%3A+%22BUY%22%2C+%22quantity%22%3A+%220.01%22%2C+%22type%22%3A+%22LIMIT%22%2C+%22price%22%3A+%22110464.0%22%2C+%22timeInForce%22%3A+%22GTC%22%7D%5D×tamp=1756127492040&signature=160e01e70194b4f48f9f4c4c7b933c7755cc82aef08513eb6c6e974a4850c932
[{'orderId': 5609573324, 'symbol': 'BTCUSDT', 'status': 'NEW', 'clientOrderId': 'i2SDQszYoiIQ3Uw2hJFqAg', 'price': '110575.10', 'avgPrice': '0.00', 'origQty': '0.010', 'executedQty': '0.000', 'cumQty': '0.000', 'cumQuote': '0.00000', 'timeInForce': 'GTC', 'type': 'LIMIT', 'reduceOnly': False, 'closePosition': False, 'side': 'BUY', 'positionSide': 'BOTH', 'stopPrice': '0.00', 'workingType': 'CONTRACT_PRICE', 'priceProtect': False, 'origType': 'LIMIT', 'priceMatch': 'NONE', 'selfTradePreventionMode': 'EXPIRE_MAKER', 'goodTillDate': 0, 'updateTime': 1756127492237}, {'orderId': 5609573323, 'symbol': 'BTCUSDT', 'status': 'NEW', 'clientOrderId': 'KWt009fJyFVrvBu8Y4xD7x', 'price': '110464.00', 'avgPrice': '0.00', 'origQty': '0.010', 'executedQty': '0.000', 'cumQty': '0.000', 'cumQuote': '0.00000', 'timeInForce': 'GTC', 'type': 'LIMIT', 'reduceOnly': False, 'closePosition': False, 'side': 'BUY', 'positionSide': 'BOTH', 'stopPrice': '0.00', 'workingType': 'CONTRACT_PRICE', 'priceProtect': False, 'origType': 'LIMIT', 'priceMatch': 'NONE', 'selfTradePreventionMode': 'EXPIRE_MAKER', 'goodTillDate': 0, 'updateTime': 1756127492237}]
## aiohttp
POST https://testnet.binancefuture.com/fapi/v1/batchOrders?batchOrders=%5B%7B%22symbol%22%3A+%22BTCUSDT%22%2C+%22side%22%3A+%22BUY%22%2C+%22quantity%22%3A+%220.01%22%2C+%22type%22%3A+%22LIMIT%22%2C+%22price%22%3A+%22110575.1%22%2C+%22timeInForce%22%3A+%22GTC%22%7D%2C+%7B%22symbol%22%3A+%22BTCUSDT%22%2C+%22side%22%3A+%22BUY%22%2C+%22quantity%22%3A+%220.01%22%2C+%22type%22%3A+%22LIMIT%22%2C+%22price%22%3A+%22110464.0%22%2C+%22timeInForce%22%3A+%22GTC%22%7D%5D×tamp=1756127492279&signature=daf8a1138f7582a8d9568edca1e222978c7bb065fe717c06e16d0a512b583a6d
{'code': -1022, 'msg': 'Signature for this request is not valid.'}
The results should be the same
Logs/tracebacks
None
Python Version
$ python --version
Python 3.12.3
aiohttp Version
$ python -m pip show aiohttp
Name: aiohttp
Version: 3.12.15
Summary: Async http client/server framework (asyncio)
Home-page: https://github.com/aio-libs/aiohttp
Author:
Author-email:
License: Apache-2.0 AND MIT
Location: /root/NexusTrader/.venv/lib/python3.12/site-packages
Requires: aiohappyeyeballs, aiosignal, attrs, frozenlist, multidict, propcache, yarl
Required-by: ccxt, nexustrader
multidict Version
$ python -m pip show multidict
Name: multidict
Version: 6.2.0
Summary: multidict implementation
Home-page: https://github.com/aio-libs/multidict
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache 2
Location: /root/NexusTrader/.venv/lib/python3.12/site-packages
Requires:
Required-by: aiohttp, picows, yarl
propcache Version
$ python -m pip show propcache
Name: propcache
Version: 0.3.1
Summary: Accelerated property cache
Home-page: https://github.com/aio-libs/propcache
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache-2.0
Location: /root/NexusTrader/.venv/lib/python3.12/site-packages
Requires:
Required-by: aiohttp, yarl
yarl Version
$ python -m pip show yarl
Name: yarl
Version: 1.20.0
Summary: Yet another URL library
Home-page: https://github.com/aio-libs/yarl
Author: Andrew Svetlov
Author-email: andrew.svetlov@gmail.com
License: Apache-2.0
Location: /root/NexusTrader/.venv/lib/python3.12/site-packages
Requires: idna, multidict, propcache
Required-by: aiohttp, ccxt
OS
Linux Ubuntu
Related component
Client
Additional context
No response
Code of Conduct
- I agree to follow the aio-libs Code of Conduct
Metadata
Metadata
Assignees
Labels
needs-infoIssue is lacking sufficient information and will be closed if not providedIssue is lacking sufficient information and will be closed if not provided