Access URL
Sandbox Environment
- Public stream: wss://stream-glb.sim.hashkeydev.com/quote/ws/v1
- Private stream: wss://stream-glb.sim.hashkeydev.com/api/v1/ws/{listenKey}
Production Environment
- Public stream: wss://stream-glb.hashkey.com/quote/ws/v1
- Private stream: wss://stream-glb.hashkey.com/api/v1/ws/{listenKey}
Heartbeat check
When a user's websocket client application successfully connects to HashKey websocket server, the client is required to initiate a periodic heartbeat message (ping message) containing the sessionID and recommended to ping the server every 10s, which is used to keep alive the connection
Ping message format is as follows:
{
"ping":1691473241907
}
When the user receives the above Ping message from the websocket Client application, it will return a Pong message containing the current timestamp.
The Pong message format is as follows:
{
"pong":1691473283965
}
Automatic disconnection mechanism
When the websocket server sends two Ping messages in a row, but neither Pong is returned, the websocket link will be automatically disconnected.
Websocket 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": "BTCUSDT",
"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": "BTCUSDT",
"topic": "trade",
"event": "cancel",
"params": {
"binary": False
},
"id": 1
}
self._ws.send(json.dumps(unsub))
def connect(self):
base_url = 'wss://stream-glb.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()
import hashlib
import hmac
import json
import time
import websocket
import logging
import threading
import requests
import datetime
########################################################################################################################
# Test Websocket API
# Copyright: Hashkey Trading 2024
########################################################################################################################
class WebSocketClient:
def __init__(self, user_key, user_secret, 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-glb.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-glb.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": "BTCUSDT",
"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": "BTCUSDT",
"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-glb.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()
Currently only support JSON format. Binary format is not yet available
Public Stream
Spot and Futures uses the same Websocket subscription
Kline_$interval
Update frequency: 300ms
Subscription request parameters
PARAMETER | TYPE | Req'd | Example values | DESCRIPTION |
---|---|---|---|---|
symbol | STRING | Y | BTCUSDT, ETHUSDT-PERPETUAL | 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:
sub = {
"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, // event Time
"s": "BTCUSDT", // symbol
"sn": "BTCUSDT", // symbol Name
"c": "10002", // close Price
"h": "10002", // high Price
"l": "10002", // low Price
"o": "10002", // open Price
"v": "0" // base Asset Volume
}
],
"f": true, // is it the first return
"sendTime": 1688199705619,
"shared": false,
"id": "1"
}
Realtimes
Update frequency: 500ms
Subscription request parameters
PARAMETER | TYPE | Req'd | Example values | DESCRIPTION |
---|---|---|---|---|
symbol | STRING | Y | BTCUSDT, ETHUSDT-PERPETUAL | Currency pair |
topic | STRING | Y | realtimes | Topic name, default: "realtimes" |
event | STRING | Y | sub | Event Type |
params | Array | Y | "binary": false "" | Parameter |
Request Example:
sub = {
"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 | Latest transaction record 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, // event Time
"s": "BTCUSDT", // symbol
"sn": "BTCUSDT", // symbol Name
"c": "10002", // close Price
"h": "10002", // high Price
"l": "10002", // low Price
"o": "10002", // open Price
"v": "0", // total traded base asset volume
"qv": "0", // total traded quote asset volume
"m": "0", // 24H Range
"e": 301 // trade Id
}
],
"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, ETHUSDT-PERPETUAL | Currency pair |
topic | STRING | Y | trade | Topic name, default: "trade" |
event | STRING | Y | sub | Event type |
params | STRING | Y | "binary": False | Parameter |
Request Example:
sub = {
"symbol": "BTCUSDT",
"topic": "trade",
"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 | 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 | Is the buyer the market maker? 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", // transaction ID
"t": 1687271825415, // time
"p": "10001", // price
"q": "0.001", // quantity
"m": false // true: maker, false: taker
},
{
"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 100
Update frequency: 300ms
Request Example:
Subscription request parameters
PARAMETER | TYPE | Req'd | Example values | DESCRIPTION |
---|---|---|---|---|
symbol | STRING | Y | BTCUSDT, ETHUSDT-PERPETUAL | Currency pair |
topic | STRING | Y | depth | Topic name, default: "depth" |
event | STRING | Y | sub | Event type |
params | STRING | Y | "binary": False | Parameter |
sub = {
"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 | Exchange id |
data.s | STRING | BTUSDT | Currency pair |
data.t | INT64 | 1688199202314 | Timestamp in milliseconds |
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 |
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", // symbol
"t": 1688199202314, // time
"v": "6881_18", // base Asset Volume
"b": [], // bids
"a": [
[
"10004", // price
"0.001" // quantity
],
[
"18000",
"0.05"
],
[
"27000",
"0.01"
],
[
"28000",
"0.09"
]
],
"o": 0
}
],
"f": true, // is it the first return
"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 extend its validity for 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 listenKeyand extend its validity for 60 minutes.
- User feeds are accessible via /api/v1/ws/ (e.g. wss://#HOST/api/v1/ws/)
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.sim.obibiz.com/api/v1/ws/{listenkey} and click "Connect"
Payload: Account Update
Update frequency: Realtime
Subscription request parameters
No
Subscription Request Example
No
Subscription return parameter
Spot Account balance change
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
// outboundAccountInfo // Trading Account
// outboundCustodyAccountInfo // Custody Account (To be released soon)
// outboundFiatAccountInfo // Fiat Account (To be released soon)
"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
}
]
}
]
Future Account balance change
[
{
"e": "outboundContractAccountInfo", // event type
// outboundContractAccountInfo
"E": "1714717314118", // event time
"T": true, // can trade
"W": true, // can withdraw
"D": true, // can deposit
"B": [ // balances changed
{
"a": "USDT", // asset
"f": "474960.65", // free amount
"l": "24835.178056020383226869", // locked amount
"r": "" // to be released
}
]
}
]
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
Spot account Execution Report
[
{
"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, // is this trade the maker side?
"O": 1499405658657, // order creation time
"Z": "0.00000000", // cumulative quote asset transacted quantity
"x": "USER_CANCEL" // order cancel reject reason
}
]
Future account Execution Report
[
{
"e": "contractExecutionReport", // event type
"E": "1714716899100", // event time
"s": "ETHUSDT-PERPETUAL", // symbol
"c": "99999999980007", // client order ID
"S": "BUY", // side
"o": "LIMIT", // order type
"f": "GTC", // time in force
"q": "10000", // order quantity
"p": "3000", // order price
"X": "NEW", // current order status
"i": "1677561369711395584", // order ID
"l": "0", // last executed quantity
"z": "0", // cumulative filled quantity
"L": "", // last executed price
"n": "0", // commission amount
"N": "", // commission asset
"u": true, // is the trade normal, ignore for now
"w": true, // is the order working?
"m": false, // is this trade the maker side?
"O": "1714716899068", // order creation time
"Z": "0", // cumulative quote asset transacted quantity
"C": false, // is close, Is the buy close or sell close
"V": "26105.5", // average executed price
"reqAmt": "0", // requested cash amount
"d": "", // execution ID
"r": "10000", // unfilled quantity
"v": "5", // leverage
"P": "30000", // Index price
"lo": true, // Is liquidation Order
"lt": "LIQUIDATION_MAKER" // Liquidation type "LIQUIDATION_MAKER_ADL", "LIQUIDATION_MAKER", "LIQUIDATION_TAKER" (To be released)
}
]
Payload: Ticket push
Update frequency: Realtime
Subscription request parameters
No
Subscription Request Example
No
Subscription return parameter
Spot account Ticket push
[
{
"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
"a": "1286424214388204801", // accountId
"A": 0, // ignore
"m": false, // isMaker
"S": "SELL" // side SELL or BUY
}
]
Future account Ticket push
[
{
"e": "ticketInfo", // event type
"E": "1714717146971", // event time (latest order info update time when the message is created)
"s": "BTCUSDT-PERPETUAL", // symbol
"q": "10.00", // quantity
"t": "1714717146957", // order matching time
"p": "61500.00", // price
"T": "1677563449212944384", // ticketId
"o": "1677563449087935232", // orderId
"c": "99999999980091", // clientOrderId
"a": "1649292498437183232", // accountId
"m": false, // isMaker
"S": "BUY" // side SELL or BUY
}
]
Payload: Position push
Update frequency: Realtime
Subscription request parameters
No
Subscription Request Example
No
Future account Position push
[
{
"e": "outboundContractPositionInfo", // event type
"E": "1715224789008", // event time
"A": "1649292498437183234", // account ID
"s": "ETHUSDT-PERPETUAL", // symbol
"S": "LONG", // side, LONG or SHORT
"p": "3212.78", // avg Price
"P": "22902", // total position
"a": "22902", // available position
"f": "0", // liquidation price
"m": "13680.323", // portfolio margin
"r": "-3.8819", // realised profit and loss (Pnl)
"up": "-4909.9255", // unrealized profit and loss (unrealizedPnL)
"pr": "-0.3589", // profit rate of current position
"pv": "73579.09", // position value (USDT)
"v": "5.00", // leverage
"mt": "CROSS", // position type, only CROSS, ISOLATED later will support
"mm": "0" // min margin
}
]