> ## Documentation Index
> Fetch the complete documentation index at: https://docs.whitebit.com/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.

## 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={"theme":{"light":"github-light","dark":"github-dark"}}
        import websocket
        import json

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

      <Tab title="wscat">
        ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
        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={"theme":{"light":"github-light","dark":"github-dark"}}
        # 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={"theme":{"light":"github-light","dark":"github-dark"}}
        > {"id": 1, "method": "lastprice_subscribe", "params": ["BTC_USDT"]}
        ```
      </Tab>
    </Tabs>

    **Subscribe confirmation:**

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

    **Update message:**

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "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={"theme":{"light":"github-light","dark":"github-dark"}}
        # 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={"theme":{"light":"github-light","dark":"github-dark"}}
        > {"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. To drop a single market while keeping the others, send `depth_unsubscribe` with that market name in `params` instead. Full parameter details: [Depth channel](/websocket/market-streams/depth).

    **Update message:**

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "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={"theme":{"light":"github-light","dark":"github-dark"}}
        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": 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={"theme":{"light":"github-light","dark":"github-dark"}}
        # 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={"theme":{"light":"github-light","dark":"github-dark"}}
    {"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={"theme":{"light":"github-light","dark":"github-dark"}}
        # 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={"theme":{"light":"github-light","dark":"github-dark"}}
        > {"id": 4, "method": "balanceSpot_subscribe", "params": ["USDT", "BTC"]}
        ```
      </Tab>
    </Tabs>

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

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {
      "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={"theme":{"light":"github-light","dark":"github-dark"}}
        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={"theme":{"light":"github-light","dark":"github-dark"}}
        > {"id": 0, "method": "ping", "params": []}
        ```
      </Tab>
    </Tabs>

    **Expected response:**

    ```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
    {"id": 0, "result": "pong", "error": null}
    ```

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

The six steps above cover a single connection. Production integrations need automatic reconnection and state recovery, covered next.

## 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={"theme":{"light":"github-light","dark":"github-dark"}}
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": 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 an 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 the `past_update_id` chain on incremental deltas; keepalive snapshots (`params[0]` is `true`) are full resets, not gaps. |
| 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={"theme":{"light":"github-light","dark":"github-dark"}}
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>
