Access URL
Sandbox Environment
- Public stream:
wss://stream-pro.sim.hashkeydev.com/quote/ws/v1
- Private stream:
wss://stream-pro.sim.hashkeydev.com/api/v1/ws/{listenKey}
Production Environment
- Public stream:
wss://stream-pro.hashkey.com/quote/ws/v1
- Private stream:
wss://stream-pro.hashkey.com/api/v1/ws/{listenKey}
Note: Replace {listenKey}
with your actual listen key obtained from the API.
Heartbeat check
When a user's websocket client application successfully connects to HashKey websocket server, the client is recommended to initiate a periodic heartbeat message (ping message) every 10 seconds, which is used to keep alive the connection
Ping message format is as follows:
// From Sent by the user
{
"ping": 1748503859938
}
Pong message format is as follows:
// Public Stream, return server's timestamp
{
"pong": 1748503865406
}
// Private Stream, return client's ping timestamp and channelId
{
"pong": 1748503859938,
"channelId": "02ac86fffe5fdf52-00000001-00266eb0-74a37ad40fb20d81-0cda790b"
}
Automatic disconnection mechanism (Only for Private Stream)
The websocket server will send a ping message every 30 seconds. We recommend clients respond with a pong message containing the same timestamp. It's not necessary to include the channelId.
A mismatched pong timestamp will not affect the connection — we mainly care about receiving the pong itself, which indicates the connection is alive. (This mechanism is primarily used for internal latency calculation and statistics.)
If the client has no heartbeat activity for 60 minutes, the session will be closed by the server.
// From Websocket Server
{
"ping": 1748504490208,
"channelId": "02ac86fffe5fdf52-00000001-00266eb0-74a37ad40fb20d81-0cda790b"
}
// Respond from client
{
"pong": 1748504490208
}
Python Public Stream Sample
import hashlib
import hmac
import json
import time
import websocket
import logging
import threading
########################################################################################################################
# Test Websocket API
# Copyright: Hashkey Trading 2023
########################################################################################################################
class WebSocketClient:
def __init__(self):
self._logger = logging.getLogger(__name__)
self._ws = None
self._ping_thread = None
def _on_message(self, ws, message):
self._logger.info(f"Received message: {message}")
data = json.loads(message)
if "pong" in data:
# Received a pong message from the server
self._logger.info("Received pong message")
# Handle the received market data here
def _on_error(self, ws, error):
self._logger.error(f"WebSocket error: {error}")
def _on_close(self, ws):
self._logger.info("Connection closed")
def _on_open(self, ws):
self._logger.info("Subscribe topic")
sub = {
"symbol": "BTCUSD",
"topic": "trade",
"event": "sub",
"params": {
"binary": False
},
"id": 1
}
ws.send(json.dumps(sub))
# Start the ping thread after connecting
self._start_ping_thread()
def _start_ping_thread(self):
def send_ping():
while self._ws:
ping_message = {
"ping": int(time.time() * 1000) # Send a timestamp as the ping message
}
self._ws.send(json.dumps(ping_message))
self._logger.info(f"Send ping message: {ping_message}")
time.sleep(5)
self._ping_thread = threading.Thread(target=send_ping)
self._ping_thread.daemon = True
self._ping_thread.start()
def unsubscribe(self):
if self._ws:
self._logger.info("Unsubscribe topic")
unsub = {
"symbol": "BTCUSD",
"topic": "trade",
"event": "cancel",
"params": {
"binary": False
},
"id": 1
}
self._ws.send(json.dumps(unsub))
def connect(self):
base_url = 'wss://stream-pro.sim.hashkeydev.com'
endpoint = 'quote/ws/v1'
stream_url = f"{base_url}/{endpoint}"
self._logger.info(stream_url)
self._logger.info(f"Connecting to {stream_url}")
self._ws = websocket.WebSocketApp(stream_url,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close)
self._ws.on_open = self._on_open
self._ws.run_forever()
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
client = WebSocketClient()
client.connect()
Python Private Stream Sample
import hashlib
import hmac
import json
import time
import websocket
import logging
import threading
import requests
import datetime
########################################################################################################################
# Test Websocket API
# Copyright: Hashkey Trading 2023
########################################################################################################################
class WebSocketClient:
def __init__(self, user_key, user_secret, subed_topic=None):
if subed_topic is None:
subed_topic = []
self.user_key = user_key
self.user_secret = user_secret
self.subed_topic = subed_topic
self.listen_key = None
self._logger = logging.getLogger(__name__)
self._ws = None
self._ping_thread = None
self.last_listen_key_extend = time.time()
def generate_listen_key(self):
params = {
'timestamp': int(time.time() * 1000),
}
api_headers = {
'X-HK-APIKEY': self.user_key,
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
signature = self.create_hmac256_signature(secret_key=self.user_secret, params=params)
params.update({
'signature': signature,
})
response = requests.post(url=f"https://api-pro.sim.hashkeydev.com/api/v1/userDataStream", headers=api_headers, data=params)
data = response.json()
if 'listenKey' in data:
self.listen_key = data['listenKey']
self._logger.info(f"Generated listen key: {self.listen_key}")
else:
raise Exception("Failed to generate listen key")
def extend_listenKey_timeLimit(self):
params = {
'timestamp': int(time.time() * 1000),
'listenKey': self.listen_key,
}
api_headers = {
'X-HK-APIKEY': self.user_key,
'content-type': 'application/x-www-form-urlencoded;charset=UTF-8',
}
signature = self.create_hmac256_signature(secret_key=self.user_secret, params=params)
params.update({
'signature': signature,
})
response = requests.put(url=f"https://api-pro.sim.hashkeydev.com/api/v1/userDataStream", headers=api_headers, data=params)
if response.status_code == 200:
self._logger.info("Successfully extended listen key validity.")
else:
self._logger.error("Failed to extend listen key validity.")
def create_hmac256_signature(self, secret_key, params, data=""):
for k, v in params.items():
data = data + str(k) + "=" + str(v) + "&"
signature = hmac.new(secret_key.encode(), data[:-1].encode(), digestmod=hashlib.sha256).hexdigest()
return signature
def _on_message(self, ws, message):
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S.%f")
self._logger.info(f"{current_time} - Received message: {message}")
data = json.loads(message)
if "pong" in data:
self._logger.info("Received pong message")
# Handle other messages here
def _on_error(self, ws, error):
self._logger.error(f"WebSocket error: {error}")
def _on_close(self, ws):
self._logger.info("Connection closed")
def _on_open(self, ws):
self._logger.info("Subscribing to topics")
for topic in self.subed_topic:
sub = {
"symbol": "BTCUSD",
"topic": topic,
"event": "sub",
"params": {
"limit": "100",
"binary": False
},
"id": 1
}
ws.send(json.dumps(sub))
self._start_ping_thread()
def _start_ping_thread(self):
def send_ping():
while self._ws:
current_time = time.time()
if current_time - self.last_listen_key_extend > 1800: # Extend listen key every 30 minutes
self.extend_listenKey_timeLimit()
self.last_listen_key_extend = current_time
ping_message = {"ping": int(time.time() * 1000)}
self._ws.send(json.dumps(ping_message))
self._logger.info(f"Sent ping message: {ping_message}")
time.sleep(5)
self._ping_thread = threading.Thread(target=send_ping)
self._ping_thread.daemon = True
self._ping_thread.start()
def unsubscribe(self):
if self._ws:
self._logger.info("Unsubscribing from topics")
for topic in self.subed_topic:
unsub = {
"symbol": "BTCUSD",
"topic": topic,
"event": "cancel_all",
"params": {
"limit": "100",
"binary": False
},
"id": 1
}
self._ws.send(json.dumps(unsub))
def connect(self):
if not self.listen_key:
self.generate_listen_key()
base_url = 'wss://stream-pro.sim.hashkeydev.com'
endpoint = f'api/v1/ws/{self.listen_key}'
stream_url = f"{base_url}/{endpoint}"
self._logger.info(f"Connecting to {stream_url}")
self._ws = websocket.WebSocketApp(stream_url,
on_message=self._on_message,
on_error=self._on_error,
on_close=self._on_close)
self._ws.on_open = self._on_open
self._ws.run_forever()
if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
user_key = "YOUR_USER_KEY"
user_secret = "YOUR_USER_SECRET"
subed_topics = ["trade"]
client = WebSocketClient(user_key, user_secret, subed_topics)
client.connect()
Public Stream
Kline_$interval
Update frequency: 300ms
Subscription request parameters
PARAMETER | TYPE | Req'd | Example values | DESCRIPTION |
---|---|---|---|---|
symbol | STRING | Y | BTCUSDT | Name of currency pair |
topic | STRING | Y | kline_1m | Topic name, default: "kline" |
event | STRING | Y | sub | Event type |
params | STRING | Y | "binary": false | Parameter |
Request Example:
{
"symbol": "BTCUSDT",
"topic": "kline_1m",
"event": "sub",
"params": {
"binary": false
},
"id": 1
}
Response content
PARAMETER | TYPE | Example values | DESCRIPTION |
---|---|---|---|
symbol | STRING | BTCUSDT | Currency pair ID |
symbolName | STRING | BTCUSDT | Currency pair name |
topic | STRING | kline | Topic name |
params | JSON Object | Request expanded parameters | |
params.realtimeInterval | STRING | 24h | Time period |
params.klineType | STRING | 1m | Kline Type |
params.binary | BOOLEAN | false | Whether it is a binary type |
data | JSON Array | Return data | |
data.t | INT64 | 1688199660000 | Timestamp in Milliseconds |
data.s | STRING | BTCUSDT | Currency pair ID |
data.sn | STRING | BTCUSDT | Currency pair name |
data.c | STRING | 10002 | Close price |
data.h | STRING | 10002 | High price |
data.l | STRING | 10002 | Low price |
data.o | STRING | 10002 | Open price |
data.v | STRING | 0 | Base Asset Volume |
f | BOOLEAN | true | Whether it is the first return value |
sendTime | INT64 | 1688199705619 | Timetamp in Milliseconds |
id | STRING | 1 | Message ID |
Response Example:
{
"symbol": "BTCUSDT",
"symbolName": "BTCUSDT",
"topic": "kline",
"params": {
"realtimeInterval": "24h",
"klineType": "1m",
"binary": "false"
},
"data": [
{
"t": 1688199660000,
"s": "BTCUSDT",
"sn": "BTCUSDT",
"c": "10002",
"h": "10002",
"l": "10002",
"o": "10002",
"v": "0"
}
],
"f": true,
"sendTime": 1688199705619,
"shared": false,
"id": "1"
}
Realtimes
Update frequency: 500ms
Subscription request parameters
PARAMETER | TYPE | Req'd | Example values | DESCRIPTION |
---|---|---|---|---|
symbol | STRING | Y | BTCUSDT | Currency pair |
topic | STRING | Y | realtimes | Topic name, default: "realtimes" |
event | STRING | Y | sub | Event Type |
params | Array | Y | "binary": false | Parameter |
Request Example:
{
"symbol": "BTCUSDT",
"topic": "realtimes",
"event": "sub",
"params": {
"binary": false
},
"id": 1
}
Response content
PARAMETER | TYPE | Example values | DESCRIPTION |
---|---|---|---|
symbol | STRING | BTCUSDT | Currency pair ID |
symbolName | STRING | BTCUSDT | Currency pair name |
topic | STRING | realtimes | Topic name |
params | JSON Object | Request expanded parameter | |
params.realtimeInterval | STRING | 24h | Time period |
params.binary | BOOLEAN | false | Whether it is a binary type |
params.dumpScale | INT64 | 10 | Number of layers in either of order book sides |
data | JSON Array | Return data | |
data.t | INT64 | 1688199300011 | Timestamp in Milliseconds |
data.s | STRING | BTCUSDT | Currency pair ID |
data.sn | STRING | BTCUSDT | Currency pair name |
data.c | STRING | 10002 | Close price |
data.h | STRING | 10002 | High price |
data.l | STRING | 10002 | Low price |
data.o | STRING | 10002 | Open price |
data.v | STRING | 0 | Volume (in base currency) |
data.qv | STRING | 0 | Volume(in quote currency) |
data.m | STRING | 0 | 24H range |
data.e | INT64 | 301 | Exchange ID |
f | BOOLEAN | true | Whether it is the first return value |
sendTime | INT64 | false | Whether to share |
id | STRING | 1 | Message ID |
Response Example:
{
"symbol": "BTCUSDT",
"symbolName": "BTCUSDT",
"topic": "realtimes",
"params": {
"realtimeInterval": "24h",
"binary": "false"
},
"data": [
{
"t": 1688199300011,
"s": "BTCUSDT",
"sn": "BTCUSDT",
"c": "10002",
"h": "10002",
"l": "10002",
"o": "10002",
"v": "0",
"qv": "0",
"m": "0",
"e": 301
}
],
"f": true,
"sendTime": 1688199337756,
"shared": false,
"id": "1"
}
Trade
Upon successful subscription to our WebSocket API, you will receive an update of the most recent 60 trades for the symbol pair subscribed.
Update frequency: 300ms
Subscription request parameters
PARAMETER | TYPE | Req'd | Example values | DESCRIPTION |
---|---|---|---|---|
symbol | STRING | Y | BTCUSDT | Currency pair |
topic | STRING | Y | trade | Topic name, default: "trade" |
event | STRING | Y | sub | Event type |
params | STRING | Y | "binary": False | Parameter |
Request Example:
{
"symbol": "BTCUSDT",
"topic": "trade",
"event": "sub",
"params": {
"binary": false
},
"id": 1
}
Response content
PARAMETER | TYPE | Example values | DESCRIPTION |
---|---|---|---|
symbol | STRING | BTCUSDT | Currency pair ID |
symbol | STRING | BTCUSDT | Currency pair name |
topic | STRING | trade | Topic name |
params | JSON Object | Request expanded parameters | |
params.realtimeInterval | STRING | 24h | Time period |
params.binary | BOOLEAN | false | Whether it is a binary type |
data | JSON Array | Return data | |
data.v | STRING | 1447335405363150849 | Transaction record ID |
data.t | STRING | 1687271825415 | Timestamp corresponding to transaction time in milliseconds |
data.p | STRING | 10001 | Traded price |
data.q | STRING | 0.001 | Traded quantity |
data.m | STRING | false | isMaker (true: maker, false: taker) |
f | BOOLEAN | true | Whether it is the first return value |
sendTime | INT64 | 1688198964293 | Timestamp in milliseconds |
shared | BOOLEAN | false | Whether to share |
id | STRING | 1 | Message ID |
Response Example:
{
"symbol": "BTCUSDT",
"symbolName": "BTCUSDT",
"topic": "trade",
"params": {
"realtimeInterval": "24h",
"binary": "false"
},
"data": [
{
"v": "1447335405363150849",
"t": 1687271825415,
"p": "10001",
"q": "0.001",
"m": false
},
{
"v": "1447337171483901952",
"t": 1687272035953,
"p": "10001.1",
"q": "0.001",
"m": true
},
{
"v": "1447337410752167937",
"t": 1687272064476,
"p": "10003",
"q": "0.001",
"m": false
},
{
"v": "1452060966291521544",
"t": 1687835156176,
"p": "10002",
"q": "0.001",
"m": false
}
],
"f": true,
"sendTime": 1688198964293,
"shared": false,
"id": "1"
}
Depth
Request the depth of the orderbook, can request up to limit of 200
Update frequency: 300ms
Subscription request parameters
PARAMETER | TYPE | Req'd | Example values | DESCRIPTION |
---|---|---|---|---|
symbol | STRING | Y | BTCUSDT | Currency pair |
topic | STRING | Y | depth | Topic name, default: "depth" |
event | STRING | Y | sub | Event type |
params | STRING | Y | "binary": False | Parameter |
Request Example:
{
"symbol": "BTCUSDT",
"topic": "depth",
"event": "sub",
"params": {
"binary": false
},
"id": 1
}
Response content
PARAMETER | TYPE | Example values | DESCRIPTION |
---|---|---|---|
symbol | STRING | BTCUSDT | Currency pair ID |
symbol | STRING | BTCUSDT | Currency pair name |
topic | STRING | depth | Topic name |
params | JSON Object | Request expanded parameters | |
params.realtimeInterval | STRING | 24h | Time period |
params.binary | BOOLEAN | false | Whether it is a binary type |
data | JSON Array | Return data | |
data.e | INT64 | 301 | Latest transaction record ID |
data.s | STRING | BTUSDT | Currency pair |
data.t | INT64 | 1688199202314 | Timestamp in milliseconds (data time) |
data.v | STRING | 6881_18 | Base asset volume |
data.a | JSON Array | ["10004", "0.001"] | Ask price and quantity |
data.b | JSON Array | ["10004", "0.001"] | Bid price and quantity |
f | BOOLEAN | true | Whether it is the first return value |
sendTime | INT64 | 1688199482822 | Timestamp in milliseconds (message sending time) |
shared | BOOLEAN | false | Whether to share |
id | STRING | 1 | Message id |
Response Example:
{
"symbol": "BTCUSDT",
"symbolName": "BTCUSDT",
"topic": "depth",
"params": {
"realtimeInterval": "24h",
"binary": "false"
},
"data": [
{
"e": 301,
"s": "BTCUSDT",
"t": 1688199202314,
"v": "6881_18",
"b": [],
"a": [
[
"10004",
"0.001"
],
[
"18000",
"0.05"
],
[
"27000",
"0.01"
],
[
"28000",
"0.09"
]
],
"o": 0
}
],
"f": true,
"sendTime": 1688199482822,
"shared": false,
"id": "1"
}
Private Stream
- A User Data Stream listenKey is valid for 60 minutes after creation.
- Doing a PUT on a listenKey will reset its validity to 60 minutes.
- Doing a DELETE on a listenKey will close the stream and invalidate the listenKey.
- Doing a POST on an account with an active listenKey will return the currently active listenKey and extend its validity for 60 minutes.
- User feeds are accessible via
/api/v1/ws/<listenKey>
(e.g.wss://HOST/api/v1/ws/<listenKey>
) - User feed payloads are not guaranteed to be up during busy times; make sure to order updates with E
For example in Postman, you may test our websocket with listenkey:
- Create a new request and select Websocket

- Input
wss://stream-pro.sim.hashkeydev.com/api/v1/ws/{your_listenkey_here}
and click "Connect"

Payload: Account Update
Update frequency: Realtime
Subscription request parameters: No
Subscription Request Example: No
Subscription return parameter
Whenever the account balance changes, an event outboundAccountInfo is sent containing the assets that may have been moved by the event that generated the balance change.
[
{
"e": "outboundAccountInfo", // event type
"E": 1499405658849, // event time
"T": true, // can trade
"W": true, // can withdraw
"D": true, // can deposit
"B": [ // balances changed
{
"a": "LTC", // asset
"f": "17366.18538083", // free amount
"l": "0.00000000" // locked amount
}
]
}
]
Payload: Order Update
Update frequency: Realtime
Subscription request parameters: No
Subscription Request Example: No
Subscription return parameter
Order updates are updated through the executionReport event. Check out the API docs and the relevant enum definitions below. The average price can be found by dividing Z by z.
Execution Type:
- NEW
- PARTIALLY_FILLED
- FILLED
- PARTIALLY_CANCELED
- CANCELED
- REJECTED
[
{
"e": "executionReport", // event type
"E": 1499405658658, // event time
"s": "ETHBTC", // symbol
"c": 1000087761, // client order ID
"S": "BUY", // side
"o": "LIMIT", // order type
"f": "GTC", // time in force
"q": "1.00000000", // order quantity
"p": "0.10264410", // order price
"reqAmt": "1000", // requested cash amount
"X": "NEW", // current order status
"d": "1234567890123456789" // execution ID
"i": 4293153, // order ID
"l": "0.00000000", // last executed quantity
"r": "0" // unfilled quantity
"z": "0.00000000", // cumulative filled quantity
"L": "0.00000000", // last executed price
"V": "26105.5" // average executed price
"n": "0", // commission amount
"N": null, // commission asset
"u": true, // is the trade normal, ignore for now
"w": true, // is the order working? Stops will have
"m": false, // if the order is a limit maker order
"O": 1499405658657, // order creation time
"Z": "0.00000000", // cumulative quote asset transacted quantity
"x": "USER_CANCEL" // order cancel reject reason
}
]
Payload: Ticket push
Update frequency: Realtime
Subscription request parameters: No
Subscription Request Example: No
Subscription return parameter
[
{
"e": "ticketInfo", // event type
"E": "1668693440976", // event time
"s": "BTCUSDT", // symbol
"q": "0.205", // quantity
"t": "1668693440899", // order matching time
"p": "441.0", // price
"T": "1291488620385157122", // ticketId
"o": "1291488620167835136", // orderId
"c": "1668693440093", // clientOrderId
"O": "0", // matchOrderId
"a": "1286424214388204801", // accountId
"A": 0, // ignore
"m": false, // isMaker
"S": "SELL" // side SELL or BUY
}
]