> ## Documentation Index
> Fetch the complete documentation index at: https://whitebit-mintlify-fix-broken-links-1777248521.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# WebSocket Quickstart

> Connect to the WhiteBIT WebSocket API — subscribe to real-time prices, orderbook updates, and private balance changes.

export const RegionBaseUrl = ({className = "", showBaseUrl = true}) => {
  const [region, setRegionState] = useState(() => {
    if (typeof window !== 'undefined') {
      return localStorage.getItem("api-region-preference") || "com";
    }
    return "com";
  });
  const [mounted, setMounted] = useState(false);
  const observerRef = useRef(null);
  const isSyncingRef = useRef(false);
  const updateAllContentOnPage = targetRegion => {
    try {
      const domainFrom = targetRegion === "eu" ? "whitebit.com" : "whitebit.eu";
      const domainTo = targetRegion === "eu" ? "whitebit.eu" : "whitebit.com";
      const links = document.querySelectorAll('a');
      links.forEach(link => {
        let href = link.getAttribute('href');
        if (href && href.includes(domainFrom)) {
          link.setAttribute('href', href.replace(domainFrom, domainTo));
        }
      });
      const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT, {
        acceptNode: node => {
          if (node.parentElement?.closest('.region-toggle-component')) {
            return NodeFilter.FILTER_REJECT;
          }
          return node.textContent.includes(domainFrom) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
      });
      let currentNode;
      while (currentNode = walker.nextNode()) {
        currentNode.textContent = currentNode.textContent.replace(new RegExp(domainFrom, 'g'), domainTo);
      }
      console.log(`[RegionSync] Global content updated to ${domainTo}`);
    } catch (e) {
      console.error("[RegionSync] Error updating content:", e);
    }
  };
  const updateRegion = (newRegion, source) => {
    if (region === newRegion) return;
    console.log(`[RegionBaseUrl] Updating to "${newRegion}" (Source: ${source})`);
    if (source === 'observer') {
      isSyncingRef.current = true;
      setTimeout(() => isSyncingRef.current = false, 1000);
    }
    setRegionState(newRegion);
    localStorage.setItem("api-region-preference", newRegion);
    updateAllContentOnPage(newRegion);
    if (source === 'user-click') {
      window.dispatchEvent(new CustomEvent("regionChange", {
        detail: newRegion
      }));
      attemptToUpdateNativeDropdown(newRegion, 0);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 1), 500);
      setTimeout(() => attemptToUpdateNativeDropdown(newRegion, 2), 1500);
    }
  };
  const attemptToUpdateNativeDropdown = (targetRegion, attempt) => {
    if (isSyncingRef.current) return;
    try {
      const targetUrl = targetRegion === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
      const targetDesc = targetRegion === "eu" ? "EU Server" : "Production Server";
      const selects = document.querySelectorAll('select');
      for (const select of selects) {
        if (select.innerHTML.includes('whitebit.com') || select.innerHTML.includes('whitebit.eu')) {
          select.value = targetUrl;
          select.dispatchEvent(new Event('change', {
            bubbles: true
          }));
          return;
        }
      }
      const buttons = Array.from(document.querySelectorAll('button, [role="combobox"]'));
      const serverSelector = buttons.find(btn => {
        if (btn.closest('a') || btn.closest('[class*="card"]') || btn.closest('nav')) {
          return false;
        }
        const txt = btn.textContent || "";
        const isServerDropdown = (txt.includes('Production Server') || txt.includes('EU Server') || txt.includes('WhiteBIT Global Server') || txt.includes('WhiteBIT EU Server')) && !txt.includes('Run') && !txt.includes('Send') || btn.getAttribute('role') === 'combobox';
        return isServerDropdown;
      });
      if (serverSelector) {
        const currentText = serverSelector.textContent || "";
        if (currentText.includes(targetDesc)) return;
        serverSelector.click();
        setTimeout(() => {
          const options = document.querySelectorAll('[role="option"], li, button');
          for (const opt of options) {
            const optText = opt.textContent || "";
            if (optText.includes(targetDesc) || optText.includes(targetUrl)) {
              opt.click();
              return;
            }
          }
        }, 100);
      }
    } catch (e) {
      console.error("[Sync] Error:", e);
    }
  };
  useEffect(() => {
    setMounted(true);
    updateAllContentOnPage(region);
    const handleStorageChange = e => {
      if (e.key === "api-region-preference" && e.newValue) {
        updateRegion(e.newValue, 'storage');
      }
    };
    const handleRegionChange = e => {
      if (e.detail !== region) {
        updateRegion(e.detail, 'event');
      }
    };
    window.addEventListener("storage", handleStorageChange);
    window.addEventListener("regionChange", handleRegionChange);
    observerRef.current = new MutationObserver(mutations => {
      if (isSyncingRef.current) return;
      updateAllContentOnPage(region);
      for (const mutation of mutations) {
        if (mutation.type !== 'childList' && mutation.type !== 'characterData') continue;
        const target = mutation.target;
        const el = target.nodeType === Node.TEXT_NODE ? target.parentElement : target;
        if (el && (el.getAttribute('role') === 'option' || el.closest('[role="listbox"]'))) continue;
        const text = target.textContent || "";
        if (text.includes('WhiteBIT EU Server') || text.includes('https://whitebit.eu') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'eu') updateRegion('eu', 'observer');
          }
        } else if (text.includes('WhiteBIT Global Server') || text.includes('https://whitebit.com') && text.includes('Server')) {
          if (el && el.tagName !== 'A' && !el.closest('.region-toggle-component')) {
            if (region !== 'com') updateRegion('com', 'observer');
          }
        }
      }
    });
    observerRef.current.observe(document.body, {
      childList: true,
      subtree: true,
      characterData: true
    });
    if (typeof window !== 'undefined') {
      const current = localStorage.getItem("api-region-preference");
      if (current) attemptToUpdateNativeDropdown(current, 'init');
    }
    return () => {
      window.removeEventListener("storage", handleStorageChange);
      window.removeEventListener("regionChange", handleRegionChange);
      if (observerRef.current) observerRef.current.disconnect();
    };
  }, [region]);
  const apiBaseUrl = region === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
  if (!mounted) return null;
  return <div className={`flex items-center gap-2 flex-wrap my-4 region-toggle-component ${className}`}>
            <span className="text-sm text-gray-500 dark:text-gray-400 font-mono">
                Base URL
            </span>
            <span className="text-sm text-gray-400">(</span>
            <div className="inline-flex bg-gray-100 dark:bg-gray-800 rounded-lg p-0.5 border border-gray-200 dark:border-gray-700">
                <button onClick={() => updateRegion("com", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "com" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .com
                </button>
                <button onClick={() => updateRegion("eu", "user-click")} className={`px-2 py-0.5 text-xs font-medium rounded-md transition-all ${region === "eu" ? "bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 shadow-sm" : "text-gray-500 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200"}`}>
                    .eu
                </button>
            </div>
            <span className="text-sm text-gray-400">)</span>
            {showBaseUrl && <>
                    <span className="text-sm text-gray-400">:</span>
                    <a href={apiBaseUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-primary dark:text-primary-light hover:underline">
                        {apiBaseUrl}
                    </a>
                </>}
        </div>;
};

<RegionBaseUrl />

Connect to the WhiteBIT WebSocket API for real-time streaming data. The guide targets developers building trading bots, live price feeds, or account monitoring dashboards. The walkthrough covers connecting to the WebSocket endpoint, subscribing to public price updates, subscribing to orderbook depth, authenticating for private channels, and receiving balance updates.

## Prerequisites

* A terminal with a WebSocket client (Python `websocket-client` library, or `wscat` for interactive testing)
* For private channels: a WhiteBIT API key with **Trading** permission and familiarity with [Authentication](/api-reference/authentication)
* No special setup for public channels — same zero-authentication principle as the [Market Data API](/products/market-data/overview)

<Warning>
  WhiteBIT has no public testnet or sandbox. Private channel subscriptions
  expose live account data. Store API credentials securely — never commit
  secrets to version control or expose them in shared environments.
  For risk-free balance-change testing, use Demo Tokens (DBTC/DUSDT) —
  activate from the [WhiteBIT Codes page](https://whitebit.com/codes).
</Warning>

## Connection parameters

| Detail             | Value                                             |
| ------------------ | ------------------------------------------------- |
| Endpoint           | `wss://api.whitebit.com/ws`                       |
| Protocol           | JSON-RPC 2.0 over WebSocket                       |
| Inactivity timeout | 60 seconds — send `ping` every 50 seconds or less |
| Rate limit         | 200 requests per minute per connection            |

<Steps>
  <Step title="Connect to the WebSocket endpoint">
    Establish a WebSocket connection.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        import websocket
        import json

        ws = websocket.create_connection("wss://api.whitebit.com/ws")
        print("Connected to WebSocket API")
        ```
      </Tab>

      <Tab title="wscat">
        ```bash theme={null}
        wscat -c wss://api.whitebit.com/ws
        ```
      </Tab>
    </Tabs>

    A successful connection produces no immediate response. The server is ready to receive subscription messages.

    For Go and PHP examples, see [SDKs](/sdks).
  </Step>

  <Step title="Subscribe to last price updates">
    Subscribe to real-time price updates for BTC\_USDT.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        # Subscribe to BTC_USDT last price
        subscribe_msg = {
            "id": 1,
            "method": "lastprice_subscribe",
            "params": ["BTC_USDT"]
        }
        ws.send(json.dumps(subscribe_msg))

        # Receive subscribe confirmation
        result = json.loads(ws.recv())
        print(f"Subscribe result: {result}")

        # Receive price updates
        update = json.loads(ws.recv())
        print(f"Price update: {update}")
        ```
      </Tab>

      <Tab title="wscat">
        ```bash theme={null}
        > {"id": 1, "method": "lastprice_subscribe", "params": ["BTC_USDT"]}
        ```
      </Tab>
    </Tabs>

    **Subscribe confirmation:**

    ```json theme={null}
    {"id": 1, "result": {"status": "success"}, "error": null}
    ```

    **Update message:**

    ```json theme={null}
    {
      "id": null,
      "method": "lastprice_update",
      "params": ["BTC_USDT", "65432.10"]
    }
    ```

    The `params` array contains: market name (index 0) and last traded price (index 1). Updates arrive whenever a trade executes on the pair.
  </Step>

  <Step title="Subscribe to orderbook depth">
    Subscribe to orderbook depth updates for BTC\_USDT with 5 price levels.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        # Subscribe to BTC_USDT depth, 5 levels, no aggregation
        depth_msg = {
            "id": 2,
            "method": "depth_subscribe",
            "params": ["BTC_USDT", 5, "0", True]
        }
        ws.send(json.dumps(depth_msg))

        # Receive subscribe confirmation
        result = json.loads(ws.recv())
        print(f"Depth subscribe: {result}")

        # Receive depth snapshot/update
        depth = json.loads(ws.recv())
        print(f"Depth update: {depth}")
        ```
      </Tab>

      <Tab title="wscat">
        ```bash theme={null}
        > {"id": 2, "method": "depth_subscribe", "params": ["BTC_USDT", 5, "0", true]}
        ```
      </Tab>
    </Tabs>

    **Parameters:** market name, depth limit (`1`/`5`/`10`/`20`/`30`/`50`/`100`), price aggregation interval (`"0"` = no aggregation), and multiple subscription flag. Set the flag to `true` to add a subscription alongside existing depth subscriptions. Setting the flag to `false` unsubscribes from all active depth subscriptions. Full parameter details: [Depth channel](/websocket/market-streams/depth).

    **Update message:**

    ```json theme={null}
    {
      "id": null,
      "method": "depth_update",
      "params": [
        true,
        {
          "asks": [["65450.00", "0.500"], ["65460.00", "1.200"]],
          "bids": [["65430.00", "0.800"], ["65420.00", "1.500"]],
          "timestamp": 1700000000,
          "update_id": 12345
        },
        "BTC_USDT"
      ]
    }
    ```

    The first element in `params` is a boolean: `true` = full snapshot, `false` = incremental update. For local orderbook maintenance, apply incremental updates to the initial snapshot.
  </Step>

  <Step title="Authenticate for private channels">
    <Note>
      Steps 1–3 cover public channels (no authentication required). Steps 4–5
      require an API key — see [Authentication](/api-reference/authentication)
      for key creation and HMAC-SHA512 signing details.
    </Note>

    Private channels (balance updates, order updates) require authentication. First obtain a WebSocket token via the REST API, then send an `authorize` message.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        import base64
        import hashlib
        import hmac
        import time
        import requests

        API_KEY = "YOUR_API_KEY"
        API_SECRET = "YOUR_API_SECRET"
        BASE_URL = "https://whitebit.com"

        # Part A — Get WebSocket token via REST API
        path = "/api/v4/profile/websocket_token"
        data = {"request": path, "nonce": str(int(time.time() * 1000))}
        data_json = json.dumps(data)
        payload_b64 = base64.b64encode(data_json.encode()).decode()
        signature = hmac.new(
            API_SECRET.encode(), payload_b64.encode(), hashlib.sha512
        ).hexdigest()
        headers = {
            "Content-Type": "application/json",
            "X-TXC-APIKEY": API_KEY,
            "X-TXC-PAYLOAD": payload_b64,
            "X-TXC-SIGNATURE": signature,
        }
        token_response = requests.post(BASE_URL + path, headers=headers, data=data_json)
        ws_token = token_response.json().get("websocket_token")
        print(f"WebSocket token obtained")

        # Part B — Send authorize message over WebSocket
        auth_msg = {
            "id": 3,
            "method": "authorize",
            "params": [ws_token, "public"]
        }
        ws.send(json.dumps(auth_msg))
        auth_result = json.loads(ws.recv())
        print(f"Auth result: {auth_result}")
        ```
      </Tab>

      <Tab title="wscat">
        ```bash theme={null}
        # First obtain a token via REST (see Python example),
        # then send in wscat:
        > {"id": 3, "method": "authorize", "params": ["YOUR_WS_TOKEN", "public"]}
        ```
      </Tab>
    </Tabs>

    **Token endpoint:** `POST /api/v4/profile/websocket_token` (authenticated REST call).

    **Authorization response:**

    ```json theme={null}
    {"id": 3, "result": {"status": "success"}, "error": null}
    ```
  </Step>

  <Step title="Subscribe to spot balance updates">
    After authentication, subscribe to real-time spot balance changes.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        # Subscribe to USDT and BTC balance updates
        balance_msg = {
            "id": 4,
            "method": "balanceSpot_subscribe",
            "params": ["USDT", "BTC"]
        }
        ws.send(json.dumps(balance_msg))

        # Receive subscribe confirmation
        result = json.loads(ws.recv())
        print(f"Balance subscribe: {result}")

        # Balance updates arrive when trades execute or transfers occur
        update = json.loads(ws.recv())
        print(f"Balance update: {update}")
        ```
      </Tab>

      <Tab title="wscat">
        ```bash theme={null}
        > {"id": 4, "method": "balanceSpot_subscribe", "params": ["USDT", "BTC"]}
        ```
      </Tab>
    </Tabs>

    **Update message (triggered by a trade or transfer):**

    ```json theme={null}
    {
      "id": null,
      "method": "balanceSpot_update",
      "params": [
        {
          "USDT": {
            "available": "985.50",
            "freeze": "14.50"
          }
        }
      ]
    }
    ```

    Key fields: `available` (funds ready for trading), `freeze` (funds locked in open orders).
  </Step>

  <Step title="Send a keepalive ping">
    The server closes the connection after 60 seconds of inactivity. Send a ping message to verify the connection is alive.

    <Tabs>
      <Tab title="Python">
        ```python theme={null}
        ping_msg = {"id": 0, "method": "ping", "params": []}
        ws.send(json.dumps(ping_msg))
        pong = json.loads(ws.recv())
        print(f"Ping result: {pong}")
        ```
      </Tab>

      <Tab title="wscat">
        ```bash theme={null}
        > {"id": 0, "method": "ping", "params": []}
        ```
      </Tab>
    </Tabs>

    **Expected response:**

    ```json theme={null}
    {"id": 0, "result": "pong", "error": null}
    ```

    In production, send a ping every 50 seconds or less to prevent disconnection.
  </Step>
</Steps>

The walkthrough covered connecting to the WebSocket API, receiving real-time price updates and orderbook depth on public channels, authenticating for private data, and subscribing to balance updates. The WebSocket API provides the real-time data layer for trading bots, price feeds, and account monitoring.

## Reconnection and state recovery

WebSocket connections can drop due to network issues or server maintenance. Production integrations require automatic reconnection, re-authentication, and state recovery logic.

### Reconnection with exponential backoff

```python theme={null}
import asyncio, json, random, time, websockets, requests, base64, hashlib, hmac

API_KEY = "YOUR_API_KEY"
API_SECRET = "YOUR_API_SECRET"
BASE_URL = "https://whitebit.com"

# Channels to subscribe after every (re)connect
PUBLIC_SUBS = [
    {"method": "depth_subscribe", "params": ["BTC_USDT", 20, "0", True]},
    {"method": "lastprice_subscribe", "params": ["BTC_USDT"]},
]
PRIVATE_SUBS = [
    {"method": "balanceSpot_subscribe", "params": ["USDT", "BTC"]},
    {"method": "ordersPending_subscribe", "params": ["BTC_USDT"]},
]

def get_ws_token():
    """Fetch a fresh WebSocket token via the REST API."""
    path = "/api/v4/profile/websocket_token"
    data = json.dumps({"request": path, "nonce": str(int(time.time() * 1000))})
    payload = base64.b64encode(data.encode()).decode()
    sig = hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha512).hexdigest()
    headers = {
        "Content-Type": "application/json",
        "X-TXC-APIKEY": API_KEY,
        "X-TXC-PAYLOAD": payload,
        "X-TXC-SIGNATURE": sig,
    }
    resp = requests.post(BASE_URL + path, headers=headers, data=data)
    return resp.json()["websocket_token"]

async def subscribe_all(ws):
    """Authenticate and subscribe to all channels."""
    # Authenticate for private channels
    token = get_ws_token()
    await ws.send(json.dumps({"id": 1, "method": "authorize", "params": [token, "public"]}))
    auth = json.loads(await ws.recv())
    if auth.get("error"):
        raise Exception(f"Auth failed: {auth['error']}")

    # Subscribe to all channels
    msg_id = 2
    for sub in PUBLIC_SUBS + PRIVATE_SUBS:
        await ws.send(json.dumps({"id": msg_id, **sub}))
        msg_id += 1

async def run():
    """Main loop with automatic reconnection."""
    delay = 1
    while True:
        try:
            async with websockets.connect("wss://api.whitebit.com/ws") as ws:
                delay = 1  # reset backoff on successful connection
                await subscribe_all(ws)
                while True:
                    msg = json.loads(await asyncio.wait_for(ws.recv(), timeout=55))
                    # Process updates here
        except (asyncio.TimeoutError, websockets.exceptions.ConnectionClosed):
            jitter = random.uniform(0, delay * 0.5)
            wait = min(delay + jitter, 30)
            print(f"Disconnected — reconnecting in {wait:.1f}s")
            await asyncio.sleep(wait)
            delay = min(delay * 2, 30)  # exponential backoff, max 30s

asyncio.run(run())
```

Key points:

* **Exponential backoff** — delays double on each failure (1s → 2s → 4s → ... → 30s max) with random jitter to prevent thundering herd
* **Fresh token** — obtain a new WebSocket token on every reconnect; tokens may expire during long disconnections
* **Full resubscription** — re-subscribe to all channels after reconnecting; the server does not remember previous subscriptions

For a production-grade implementation with fill monitoring, grid strategy logic, and error recovery, see [Building a Trading Bot — Auto-resubscription](/guides/building-a-trading-bot#auto-resubscription-after-reconnect).

### State recovery after reconnect

Different channels use different update models. The recovery strategy depends on the channel type:

| Update model             | Channels                                                                                                                                                                                             | Recovery strategy                                                                                                                                                         |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Snapshot on subscribe    | [Depth](/websocket/market-streams/depth)                                                                                                                                                             | First message after subscribe is a full snapshot — local state auto-recovers. Apply incremental updates from subsequent messages. Detect gaps via `update_id` sequencing. |
| Periodic full snapshot   | [Positions](/websocket/account-streams/positions), [Borrows](/websocket/account-streams/borrows), [Market](/websocket/market-streams/market), [Market Today](/websocket/market-streams/market-today) | Full state arrives within 1–1.5s of subscribing — no gap to fill.                                                                                                         |
| Incremental delta        | [Balance Spot](/websocket/account-streams/balance-spot), [Balance Margin](/websocket/account-streams/balance-margin)                                                                                 | **Query first, then subscribe.** Send a one-time query (`balanceSpot_request` / `balanceMargin_request`) to get full state, then subscribe for incremental changes.       |
| Event stream             | [Orders Pending](/websocket/account-streams/orders-pending), [Orders Executed](/websocket/account-streams/orders-executed), [Deals](/websocket/account-streams/deals)                                | **Query first, then subscribe.** Events during disconnection are lost. Use the one-time query operation or the REST equivalent to backfill, then subscribe.               |
| Event-only (no snapshot) | [Margin Positions Events](/websocket/account-streams/margin-positions-events), [Borrows Events](/websocket/account-streams/borrows-events)                                                           | Pair with the snapshot channel ([Positions](/websocket/account-streams/positions) / [Borrows](/websocket/account-streams/borrows)) for complete state awareness.          |
| Simple replacement       | [Last Price](/websocket/market-streams/lastprice), [Book Ticker](/websocket/market-streams/book-ticker), [Kline](/websocket/market-streams/kline)                                                    | Each message replaces previous value — no gap handling needed.                                                                                                            |

For incremental-delta and event-stream channels, use a **query-then-subscribe** pattern:

```python theme={null}
async def recover_and_subscribe(ws):
    """Query current state, then subscribe for live updates."""
    # Step 1 — query full state
    await ws.send(json.dumps({
        "id": 10, "method": "balanceSpot_request", "params": []
    }))
    snapshot = json.loads(await ws.recv())
    # Initialize local state from snapshot["result"]

    # Step 2 — subscribe for incremental updates
    await ws.send(json.dumps({
        "id": 11, "method": "balanceSpot_subscribe", "params": ["USDT", "BTC"]
    }))
```

## What's next

<CardGroup cols={3}>
  <Card title="Market Data Overview" icon="chart-bar" href="/products/market-data/overview">
    REST API counterpart — polling-based market data for simpler integrations.
  </Card>

  <Card title="Building a Trading Bot" icon="robot" href="/guides/building-a-trading-bot">
    Combine WebSocket data with REST order placement for automated trading.
  </Card>

  <Card title="WebSocket Channels" icon="satellite-dish" href="/websocket/overview">
    Full channel catalog — 10 market streams and 11 account streams with schema references.
  </Card>
</CardGroup>
