> ## 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 API

> WhiteBIT WebSocket API — connection details, quickstart examples, message format, and links to all available channels.

export const RegionBaseUrl = ({className = "", showBaseUrl = true, protocol = "https"}) => {
  const {useState, useEffect, useRef} = React;
  const EU_ENABLED = false;
  const [region, setRegionState] = useState(() => {
    if (!EU_ENABLED) return "com";
    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(() => {
    if (!EU_ENABLED) return;
    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 = protocol === "wss" ? region === "eu" ? "wss://api.whitebit.eu/ws" : "wss://wss.whitebit.com/ws" : region === "eu" ? "https://whitebit.eu" : "https://whitebit.com";
  const baseUrlLabel = protocol === "wss" ? "WebSocket URL" : "Base URL";
  if (!EU_ENABLED) {
    if (!showBaseUrl) 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">
                    {baseUrlLabel}
                </span>
                <span className="text-sm text-gray-400">:</span>
                {protocol === "wss" ? <code className="text-sm font-mono text-primary dark:text-primary-light">
                        {apiBaseUrl}
                    </code> : <a href={apiBaseUrl} target="_blank" rel="noopener noreferrer" className="text-sm font-mono text-primary dark:text-primary-light hover:underline">
                        {apiBaseUrl}
                    </a>}
            </div>;
  }
  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">
                {baseUrlLabel}
            </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>
                    {protocol === "wss" ? <code className="text-sm font-mono text-primary dark:text-primary-light">
                            {apiBaseUrl}
                        </code> : <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 protocol="wss" />

<Warning>
  **Deprecated.** `wss://api.whitebit.com/ws` is deprecated as of September 1, 2026 and stops accepting connections on March 1, 2027. Connect to `wss://wss.whitebit.com/ws` instead. Only the host changes — see [Host migration](#host-migration).
</Warning>

## Connection

WebSocket endpoint: **`wss://wss.whitebit.com/ws`**

The API is based on [JSON-RPC](https://www.jsonrpc.org/specification) over the WebSocket protocol.

The API returns time in Unix-time format.

### Host migration

The Global WebSocket host moved from `api.whitebit.com` to `wss.whitebit.com` on September 1, 2026. Both hosts serve the same channels over the same `/ws` path during a 6-month transition window.

| Host | Status |
| - | - |
| `wss://wss.whitebit.com/ws` | Current. Use for all connections. |
| `wss://api.whitebit.com/ws` | Deprecated September 1, 2026. Rejects connections from March 1, 2027. |

Replace the host string in the WebSocket client and redeploy. Nothing in the application protocol changes:

* The `/ws` path is unchanged.
* The JSON-RPC request and response format is unchanged.
* Every subscription, query, and order-management method name is unchanged.
* The `authorize` flow and the token format are unchanged — see [WebSocket Authentication](/websocket/authentication).
* Connection and request limits are unchanged — see [WebSocket Rate Limits & Error Codes](/websocket/rate-limits).

One item sits outside the application protocol: a network that allowlists destination hosts must permit `wss.whitebit.com` before the switch.

The legacy host stays fully functional until March 1, 2027, so the switch does not need a coordinated cutover. An integration that resolves the host from configuration needs a configuration change only. An integration that hardcodes the host in more than one place — a reconnect helper, a failover list, a client default — must update every occurrence, because a missed entry keeps working until March 1, 2027 and fails then rather than at deploy time.

***

## Quickstart

### Prerequisites

* A WebSocket client (browser, Node.js, Python, or similar)

<Note>
  No API key is required for public channels.
</Note>

### Step 1: Establish a connection

Open a WebSocket connection to the endpoint.

<CodeGroup>
  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  const socket = new WebSocket("wss://wss.whitebit.com/ws");

  socket.onopen = () => {
    console.log("Connected");
  };

  socket.onmessage = (event) => {
    const data = JSON.parse(event.data);
    console.log("Received:", data);
  };
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import asyncio
  import websockets
  import json

  async def connect():
      async with websockets.connect("wss://wss.whitebit.com/ws") as ws:
          print("Connected")
          async for message in ws:
              data = json.loads(message)
              print("Received:", data)

  asyncio.run(connect())
  ```
</CodeGroup>

The connection opens when the WebSocket handshake completes.

### Step 2: Keep the connection alive

The server closes the connection after 60 seconds of inactivity. Send a ping message every 50 seconds to keep the connection alive.

Add the following to the connection established in Step 1:

<CodeGroup>
  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  setInterval(() => {
    if (socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify({
        id: 0,
        method: "ping",
        params: [],
      }));
    }
  }, 50000);
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import asyncio
  import websockets
  import json

  async def connect():
      async with websockets.connect("wss://wss.whitebit.com/ws") as ws:
          async def keepalive():
              while True:
                  await asyncio.sleep(50)
                  await ws.send(json.dumps({"id": 0, "method": "ping", "params": []}))

          asyncio.create_task(keepalive())
          async for message in ws:
              data = json.loads(message)
              print("Received:", data)

  asyncio.run(connect())
  ```
</CodeGroup>

**Expected response:**

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

### Step 3: Subscribe to last price updates

Subscribe to the [Last Price](/websocket/market-streams/lastprice) channel to receive real-time price updates for one or more markets.

Send the subscription message using the connection from Step 1:

<CodeGroup>
  ```javascript JavaScript theme={"theme":{"light":"github-light","dark":"github-dark"}}
  socket.send(JSON.stringify({
    id: 1,
    method: "lastprice_subscribe",
    params: ["BTC_USDT", "ETH_USDT"]
  }));
  ```

  ```python Python theme={"theme":{"light":"github-light","dark":"github-dark"}}
  import asyncio
  import websockets
  import json

  async def connect():
      async with websockets.connect("wss://wss.whitebit.com/ws") as ws:
          async def keepalive():
              while True:
                  await asyncio.sleep(50)
                  await ws.send(json.dumps({"id": 0, "method": "ping", "params": []}))

          asyncio.create_task(keepalive())

          await ws.send(json.dumps({
              "id": 1,
              "method": "lastprice_subscribe",
              "params": ["BTC_USDT", "ETH_USDT"]
          }))

          async for message in ws:
              data = json.loads(message)
              print("Received:", data)

  asyncio.run(connect())
  ```
</CodeGroup>

**Expected subscription response:**

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

### Step 4: Receive update events

After a successful subscription, the server sends update events every second. Each event contains the market name and the latest price.

**Example update event:**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{
  "id": null,
  "method": "lastprice_update",
  "params": ["BTC_USDT", "67500.50"]
}
```

The `params` array contains `[market_name, last_price]`. Parse each `lastprice_update` event to display real-time prices.

***

## Message Format

### Request message

| Field | Type | Description |
| - | - | - |
| `id` | Integer | Unique identifier to match the response to the request |
| `method` | String | Name of the method to call |
| `params` | Array | Parameters for the method |

<Warning>The connection will be closed if invalid JSON is sent.</Warning>

**Types of request messages:**

* **Query** — one-time requests (`ping`, `candles_request`, `balanceSpot_request`, etc.)
* **Subscription** — streaming updates (`candles_subscribe`, `lastprice_subscribe`, `balanceSpot_subscribe`, etc.). Repeating a subscription cancels the previous one for the same data type.

### Response message

| Field | Type | Description |
| - | - | - |
| `id` | Integer | ID of the original request |
| `result` | Object / null | `null` on failure; see channel docs for success payloads |
| `error` | Object / null | `null` on success; error object on failure |

**Types of response messages:**

* **Query result** — direct response to a query request
* **Subscription status** — success or failure of a subscription request
* **Update events** — pushed by the server when subscribed data changes

### Examples

**Query — ping/pong:**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
// ⤴️ Request
{ "id": 0, "method": "ping", "params": [] }

// ⤵️ Response
{ "id": 0, "result": "pong", "error": null }
```

**Subscription:**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
// ⤴️ Request
{ "id": 0, "method": "candles_subscribe", "params": [] }

// ⤵️ Response
{ "id": 0, "result": { "status": "success" }, "error": null }
```

**Update event:**

```json theme={"theme":{"light":"github-light","dark":"github-dark"}}
{ "id": null, "method": "candles_update", "params": [] }
```

***

For connection limits, request limits, and error codes, see [WebSocket Rate Limits & Error Codes](/websocket/rate-limits).

Private channels require authorization. See [WebSocket Authentication](/websocket/authentication) for the full setup flow.

***

## Channels

<CardGroup cols={2}>
  <Card title="Market Streams" icon="chart-line" href="/websocket/market-streams/overview">
    Public channels for market data — kline, last price, trades, depth, book ticker, and more. See [Markets & Trading Pairs](/concepts/markets) for pair notation.
  </Card>

  <Card title="Account Streams" icon="lock" href="/websocket/account-streams/overview">
    Private channels for account data — balances, orders, deals, positions, and borrows. Requires authorization. See [Balances & Transfers](/concepts/balances) for account types.
  </Card>

  <Card title="Order Management" icon="pen-to-square" href="/websocket/order-management/overview">
    Private methods that place, modify, and cancel orders on spot, margin, and futures markets (global platform only). Requires authorization. See [Order Types](/concepts/order-types) for the available types.
  </Card>
</CardGroup>

## Who uses this

* **Market makers** stream the real-time order book and fills — the public [Depth](/websocket/market-streams/depth) and [Book Ticker](/websocket/market-streams/book-ticker) channels plus the private [Deals](/websocket/account-streams/deals) account stream. See [Market Maker integration](/guides/market-maker-integration).
* Any integration reading private account events (balances, orders, deals, positions) authorizes first — see [WebSocket Authentication](/websocket/authentication).

***

## Connection best practices

Reuse a single WebSocket connection for multiple subscriptions instead of opening a new connection per channel. See [WebSocket Rate Limits & Error Codes](/websocket/rate-limits) for connection and request limits, timeout behavior, and reconnection guidance.

## Related resources

* [WebSocket Authentication](/websocket/authentication) — Authorization flow for private Account Stream channels
* [WebSocket Rate Limits & Error Codes](/websocket/rate-limits) — Connection limits, request limits, and error codes
* [API Reference Overview](/api-reference/overview) — Base URL, quickstart, and endpoint groups
* [Market Streams](/websocket/market-streams/overview) — All public channels (kline, trades, depth, book ticker)
* [Account Streams](/websocket/account-streams/overview) — Private channels for balances, orders, and positions

## Used in these guides

* [Partner Solutions](/guides/partner-solutions) — the partner router that maps use cases to programs.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.