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

PropertyValue
Protocolsocket.io v4 (not raw WebSocket)
Productionwss://bot.mywellat.com/bot/v1/market
Stagingwss://pg-bot.mywellat.com/bot/v1/market
Namespace/bot/v1/market
Transportswebsocket 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:

FieldValue
apiKeyyour wlt_bot_… public key
timestampcurrent Unix time in milliseconds, as a string
signaturehex 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 itself

Key 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 errors

A handshake timestamp more than 30 seconds from server time is rejected (AUTH_TIMESTAMP_OUT_OF_WINDOW) — pass auth as 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 reconnect

The gateway drops a socket's state on disconnect. On every connect event, 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

EventPayloadMeaning
subscribed{ pair, timeframe }Subscription active; ticks follow.
unsubscribed{ pair, timeframe }Subscription removed.
marketDatasee belowA kline tick.
error{ status: false, code, message }Every failure — always listen to it.

The 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 / closeTime are Unix milliseconds.
❗️

isFinal is the field that matters

Several ticks arrive for the same candle while it forms — each a revised snapshot. Key your candle store by startTime and upsert every tick (never append), and drive trading decisions from isFinal: true only. A crossover computed on a forming candle will fire, unfire, and refire within one interval — placing and reversing real orders each time.

Limits

LimitValue
Concurrent connections per API key5 (shared with the order book)
Active subscriptions per connection20
Distinct streams per API key30
subscribe/unsubscribe messages30 per minute per connection
Any inbound message120 per minute per connection
Failed auth handshakes5 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:

CodeMeaningYour response
AUTH_MISSING_CREDENTIALSCredentials absent from handshakeFix client. Terminal.
AUTH_TIMESTAMP_OUT_OF_WINDOWOutside the ±30 s windowSync clock, re-sign. No hot-retry.
AUTH_INVALID_SIGNATUREHMAC mismatch or unknown keyTerminal — check the UTF-8-vs-hex trap.
AUTH_KEY_REVOKEDKey deletedTerminal — alert a human.
SUB_INVALID_PAYLOADMissing pair/timeframeFix client.
SUB_UNSUPPORTED_SYMBOLNot in the supported-pairs listDrop from watchlist.
SUB_UNSUPPORTED_TIMEFRAMENot in data.timeframesFix client.
SUB_LIMIT_EXCEEDEDOver a subscription capUnsubscribe something first.
RATE_LIMITEDRate/connection limit; may carry retryAfterMsBack off for retryAfterMs.
UPSTREAM_UNAVAILABLEMarket-data source unreachableDegrade to REST polling; alert.
INTERNALUnexpected gateway errorRetry with backoff; alert if sustained.

Reliability checklist

  • auth passed 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: true only
  • Staleness watchdog running — a quiet market and a dead stream look identical from outside
  • UPSTREAM_UNAVAILABLE degrades to REST polling, with an alert
  • Last-acted startTime tracked, so replayed candles never double-fire an entry

Next: Order Book Stream — live depth on a second namespace.


Did this page help you?