Chart Price Stream
Live candlestick data over WebSocket — the socket.io handshake, subscribe protocol, kline payload, and reliability rules.
Live chart price (candlestick/kline) data for every supported pair, streamed over socket.io. Use it to drive strategy decisions without polling. For depth, see the Order Book Stream — same connection model, different namespace and payload.
Connecting
| Property | Value |
|---|---|
| Protocol | socket.io v4 (not raw WebSocket) |
| Production | wss://bot.mywellat.com/bot/v1/market |
| Staging | wss://pg-bot.mywellat.com/bot/v1/market |
| Namespace | /bot/v1/market |
| Transports | websocket only — disable HTTP long-polling |
Authentication
You authenticate once, at connection time, with the same credentials and HMAC scheme as the signed REST routes (see Authentication). Individual messages are not signed.
The signed message is fixed — method GET, path /bot/v1/market, empty body, no separators:
message = timestamp + "GET" + "/bot/v1/market"
The three values travel in the socket.io auth object:
| Field | Value |
|---|---|
apiKey | your wlt_bot_… public key |
timestamp | current Unix time in milliseconds, as a string |
signature | hex HMAC-SHA256 of the message above, keyed with your secret key |
import { io } from 'socket.io-client';
import { createHmac } from 'crypto';
const socket = io('wss://bot.mywellat.com/bot/v1/market', {
transports: ['websocket'],
auth: () => {
// A FUNCTION, not an object — re-signs on every reconnect attempt.
const timestamp = String(Date.now());
const signature = createHmac('sha256', process.env.WELLAT_BOT_SECRET_KEY)
.update(`${timestamp}GET/bot/v1/market`)
.digest('hex');
return { apiKey: process.env.WELLAT_BOT_API_KEY, timestamp, signature };
},
reconnection: true,
reconnectionDelay: 1_000,
reconnectionDelayMax: 30_000,
randomizationFactor: 0.5,
});import hashlib, hmac, os, time
import socketio
def fresh_auth():
ts = str(int(time.time() * 1000))
sig = hmac.new(
os.environ["WELLAT_BOT_SECRET_KEY"].encode(), # key = secret STRING
f"{ts}GET/bot/v1/market".encode(),
hashlib.sha256,
).hexdigest()
return {"apiKey": os.environ["WELLAT_BOT_API_KEY"], "timestamp": ts, "signature": sig}
sio = socketio.Client()
# python-socketio has no auth callback — call fresh_auth() again before
# every reconnect attempt in your own retry loop.
sio.connect(
"wss://bot.mywellat.com",
namespaces=["/bot/v1/market"],
transports=["websocket"],
auth=fresh_auth(),
)
The HMAC key is the secret key string itselfKey the HMAC with the 64-character secret as a raw UTF-8 string — never the hex decoded to bytes. If your language's HMAC helper decodes hex by default, every signature fails on an otherwise correct implementation.
Sign at connect time — and never hot-loop on auth errorsA handshake timestamp more than 30 seconds from server time is rejected (
AUTH_TIMESTAMP_OUT_OF_WINDOW) — passauthas a function so reconnects re-sign, and keep NTP running. 5 failed handshakes within 15 minutes block the key for 15 minutes.
Tools that can set handshake headers but not the auth object (Postman, plain WS clients) may instead send the same three values as x-bot-api-key, x-bot-timestamp, x-bot-signature headers.
Subscribing
Send a subscribe event (no ack — the confirmation is the subscribed event):
{ "pair": "BTCUSDT", "timeframe": "1m" }pair is case-insensitive and must be in the supported list; timeframe must be one of the values GET /bot/v1/spot/pairs returns in data.timeframes (see Trading Pairs). Send unsubscribe with the same payload to stop.
Subscriptions do not survive a reconnectThe gateway drops a socket's state on disconnect. On every
connectevent, re-subscribe to everything, then backfill the outage window from Binance REST (GET https://api.binance.com/api/v3/klines) — the stream carries no history.
Events
| Event | Payload | Meaning |
|---|---|---|
subscribed | { pair, timeframe } | Subscription active; ticks follow. |
unsubscribed | { pair, timeframe } | Subscription removed. |
marketData | see below | A kline tick. |
error | { status: false, code, message } | Every failure — always listen to it. |
The marketData payload
marketData payload{
"event": "kline",
"pair": "BTCUSDT",
"timeframe": "1m",
"candle": {
"open": "64210.15000000",
"high": "64288.00000000",
"low": "64199.42000000",
"close": "64270.31000000",
"startTime": 1783402560000,
"closeTime": 1783402619999,
"volume": "12.48310000",
"isFinal": false
}
}- OHLCV values are strings preserving exact decimal precision — parse with a decimal library, not
Number, wherever the value feeds an order. startTime/closeTimeare Unix milliseconds.
isFinalis the field that mattersSeveral ticks arrive for the same candle while it forms — each a revised snapshot. Key your candle store by
startTimeand upsert every tick (never append), and drive trading decisions fromisFinal: trueonly. A crossover computed on a forming candle will fire, unfire, and refire within one interval — placing and reversing real orders each time.
Limits
| Limit | Value |
|---|---|
| Concurrent connections per API key | 5 (shared with the order book) |
| Active subscriptions per connection | 20 |
| Distinct streams per API key | 30 |
subscribe/unsubscribe messages | 30 per minute per connection |
| Any inbound message | 120 per minute per connection |
| Failed auth handshakes | 5 per key per 15 min → 15 min block |
Subscribe only to what your strategy reads, and prefer one connection with many subscriptions over many connections with one each.
Errors
Branch on code, never on message:
| Code | Meaning | Your response |
|---|---|---|
AUTH_MISSING_CREDENTIALS | Credentials absent from handshake | Fix client. Terminal. |
AUTH_TIMESTAMP_OUT_OF_WINDOW | Outside the ±30 s window | Sync clock, re-sign. No hot-retry. |
AUTH_INVALID_SIGNATURE | HMAC mismatch or unknown key | Terminal — check the UTF-8-vs-hex trap. |
AUTH_KEY_REVOKED | Key deleted | Terminal — alert a human. |
SUB_INVALID_PAYLOAD | Missing pair/timeframe | Fix client. |
SUB_UNSUPPORTED_SYMBOL | Not in the supported-pairs list | Drop from watchlist. |
SUB_UNSUPPORTED_TIMEFRAME | Not in data.timeframes | Fix client. |
SUB_LIMIT_EXCEEDED | Over a subscription cap | Unsubscribe something first. |
RATE_LIMITED | Rate/connection limit; may carry retryAfterMs | Back off for retryAfterMs. |
UPSTREAM_UNAVAILABLE | Market-data source unreachable | Degrade to REST polling; alert. |
INTERNAL | Unexpected gateway error | Retry with backoff; alert if sustained. |
Reliability checklist
-
authpassed as a function so reconnects re-sign - Re-subscribe to everything on every
connect, then backfill from REST - Candles upserted by
startTime, never appended - Signals fire on
isFinal: trueonly - Staleness watchdog running — a quiet market and a dead stream look identical from outside
-
UPSTREAM_UNAVAILABLEdegrades to REST polling, with an alert - Last-acted
startTimetracked, so replayed candles never double-fire an entry
Next: Order Book Stream — live depth on a second namespace.
Updated 9 days ago