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

# Account Monitoring

> Monitor balances, deposits, withdrawals, and order activity across Main, Trade, and Collateral accounts using REST polling and WebSocket streams.

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 />

Monitor account state across Main, Trade, and Collateral balances using REST endpoints and WebSocket streams. Payment processing backends and trading systems consume the same account state through different mechanisms.

* **Payment processing integrations** (deposit/withdrawal monitoring) — focus on balance monitoring and deposit/withdrawal tracking; Main balance does not support WebSocket streaming, so webhooks and REST polling are the primary mechanisms.
* **Trading integrations** (bots, dashboards) — focus on balance monitoring and order activity monitoring via WebSocket for real-time state.

## Prerequisites

* A WhiteBIT API key and HMAC-SHA512 request signing — see [First API Call](/guides/first-api-call) for key creation and [Authentication](/api-reference/authentication) for the signing scheme
* For WebSocket sections: the token-based authorization flow from the [WebSocket Quickstart](/guides/websocket-quickstart)
* For webhook-based tracking: a publicly reachable consumer endpoint — see [Webhooks](/platform/webhook)

## Balance monitoring

WhiteBIT separates funds into three account types. Each has a dedicated balance endpoint:

| Account type | Endpoint                                  | Doc page                                                                           |
| ------------ | ----------------------------------------- | ---------------------------------------------------------------------------------- |
| Main         | `POST /api/v4/main-account/balance`       | [Main Balance](/api-reference/account-wallet/main-balance)                         |
| Trade (Spot) | `POST /api/v4/trade-account/balance`      | [Trading Balance](/api-reference/spot-trading/trading-balance)                     |
| Collateral   | `POST /api/v4/collateral-account/balance` | [Collateral Balance](/api-reference/collateral-trading/collateral-account-balance) |

For an explanation of account types and how funds move between accounts, see [Balances & Transfers](/concepts/balances).

Every endpoint and stream in this guide operates on the account that owns the API key — to monitor a sub-account, use the sub-account's dedicated key. A master-account key can also query a sub-account's Main, Spot, and Collateral balances via [`POST /api/v4/sub-account/balances`](/api-reference/sub-accounts/sub-account-balances). See [Sub-Accounts](/api-reference/sub-accounts/overview) for key management.

### Fetch Trade balance

<Warning>
  Store API keys securely and never commit keys to version control. Use IP whitelisting and grant keys only the minimum required permissions.
</Warning>

<Tabs>
  <Tab title="cURL">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # See /api-reference/authentication for signing details.
    curl -X POST https://whitebit.com/api/v4/trade-account/balance \
      -H "Content-Type: application/json" \
      -H "X-TXC-APIKEY: YOUR_API_KEY" \
      -H "X-TXC-PAYLOAD: BASE64_PAYLOAD" \
      -H "X-TXC-SIGNATURE: HMAC_SIGNATURE" \
      -d '{"request":"/api/v4/trade-account/balance","nonce":1700000000000}'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import base64, hashlib, hmac, json, time, requests

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

    def send_request(path, data=None):
        if data is None:
            data = {}
        data["request"] = path
        data["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,
        }
        return requests.post(BASE_URL + path, headers=headers, data=data_json).json()

    balance = send_request("/api/v4/trade-account/balance")
    for asset, info in balance.items():
        if float(info["available"]) > 0 or float(info["freeze"]) > 0:
            print(f"{asset}: available={info['available']}, freeze={info['freeze']}")
    ```
  </Tab>
</Tabs>

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

**Response fields:**

* `available` — funds ready for trading or withdrawal
* `freeze` — funds locked in open orders or pending operations

## Real-time balance via WebSocket

REST polling provides point-in-time snapshots. To receive a push message each time a balance changes, subscribe to WebSocket account streams:

| Channel                                                     | Subscribe method          | Use case                                      |
| ----------------------------------------------------------- | ------------------------- | --------------------------------------------- |
| [Balance Spot](/websocket/account-streams/balance-spot)     | `balanceSpot_subscribe`   | Trade (Spot) balance changes                  |
| [Balance Margin](/websocket/account-streams/balance-margin) | `balanceMargin_subscribe` | Collateral balance changes (Margin + Futures) |

<Note>
  Main balance does not have a WebSocket subscription channel. Use webhooks for real-time notification of the transaction types in the [webhook event catalog](/platform/webhook#webhook-methods), with `POST /api/v4/main-account/history` polling as a fallback. For a complete view of Main balance state, poll `POST /api/v4/main-account/balance`.
</Note>

### Subscribe to balance updates

The following example assumes an authenticated WebSocket session. Private channels require authorization before subscribing — see [WebSocket Quickstart — Authenticate for private channels](/guides/websocket-quickstart#authenticate-for-private-channels) for the full authentication flow. Subscribing to a private channel without authorization fails with an authorization error — check the `error` field on the subscription response.

<Tabs>
  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    import json, websockets, asyncio

    async def monitor_balance():
        async with websockets.connect("wss://api.whitebit.com/ws") as ws:
            # Authenticate first — see "Authenticate for private channels"
            # in the WebSocket Quickstart. Private channels require
            # authorization before subscribing.

            # Subscribe to spot balance changes.
            # params: [] subscribes to all assets.
            # Pass specific tickers (e.g., ["USDT", "BTC"]) to filter updates.
            await ws.send(json.dumps({
                "id": 1,
                "method": "balanceSpot_subscribe",
                "params": []
            }))

            async for message in ws:
                data = json.loads(message)
                # Subscription confirmation has a "result" key.
                # Balance updates have a "method" key set to "balanceSpot_update",
                # with "params" as an array of asset-keyed update objects.
                if data.get("method") == "balanceSpot_update":
                    for update in data["params"]:
                        for asset, info in update.items():
                            print(f"Balance change: {asset} available={info['available']} freeze={info['freeze']}")

    asyncio.run(monitor_balance())
    ```
  </Tab>

  <Tab title="wscat">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    > {"id": 1, "method": "balanceSpot_subscribe", "params": []}
    ```
  </Tab>
</Tabs>

## Deposit and withdrawal tracking

### REST polling

Query deposit and withdrawal history using the main account history endpoint:

**Endpoint:** [`POST /api/v4/main-account/history`](/api-reference/account-wallet/get-deposit-withdraw-history)

<Tabs>
  <Tab title="cURL">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -X POST https://whitebit.com/api/v4/main-account/history \
      -H "Content-Type: application/json" \
      -H "X-TXC-APIKEY: YOUR_API_KEY" \
      -H "X-TXC-PAYLOAD: BASE64_PAYLOAD" \
      -H "X-TXC-SIGNATURE: HMAC_SIGNATURE" \
      -d '{"transactionMethod":1,"request":"/api/v4/main-account/history","nonce":1700000000000}'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Uses the send_request helper from the "Fetch Trade balance" example.
    # Fetch recent deposits (transactionMethod: 1 = deposits, 2 = withdrawals)
    deposits = send_request("/api/v4/main-account/history", {
        "transactionMethod": 1,
        "limit": 50
    })
    for record in deposits.get("records", []):
        print(f"{record['ticker']}: {record['amount']} — status {record['status']}")
    ```
  </Tab>
</Tabs>

The `status` field is a numeric code — for example `3` and `7` mean credited, and deposits can sit frozen under [Travel Rule](/glossary#travel-rule) statuses `27`/`28`. See the status state machines in [Payment Integration](/guides/payment-integration).

The endpoint paginates with `limit` (default 50, max 500) and `offset` — page until fewer than `limit` records return. It accepts no date filters, and pagination is capped at `offset + limit ≤ 10000`. For reconciliation, page from `offset` 0 until reaching the last processed transaction.

### Webhook-based monitoring

For real-time deposit and withdrawal notifications, configure webhooks. Webhook delivery is best-effort — a small number of retries spaced roughly an hour apart over a window of about one day. Treat the exact cadence as not contractually guaranteed.

**Recommended reconciliation approach:**

* **Primary:** Webhooks — process events on arrival
* **Fallback:** REST polling — periodically query the history endpoint to catch any events missed during webhook downtime or after the retry window expires

See [Webhooks](/platform/webhook) for setup, signature verification, and event types. For the full deposit/withdrawal lifecycle including status state machines and fee calculation, see [Payment Integration](/guides/payment-integration).

## Order activity monitoring

### Open orders

Query active orders across all markets or filter by a specific market:

**Endpoint:** [`POST /api/v4/orders`](/api-reference/spot-trading/query-unexecuted-orders)

<Tabs>
  <Tab title="cURL">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -X POST https://whitebit.com/api/v4/orders \
      -H "Content-Type: application/json" \
      -H "X-TXC-APIKEY: YOUR_API_KEY" \
      -H "X-TXC-PAYLOAD: BASE64_PAYLOAD" \
      -H "X-TXC-SIGNATURE: HMAC_SIGNATURE" \
      -d '{"market":"BTC_USDT","request":"/api/v4/orders","nonce":1700000000000}'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Uses the send_request helper from the "Fetch Trade balance" example.
    open_orders = send_request("/api/v4/orders", {"market": "BTC_USDT"})
    print(f"Open orders: {len(open_orders)}")
    for order in open_orders:
        print(f"  {order['side']} {order['amount']} @ {order['price']} (ID: {order['orderId']})")
    ```
  </Tab>
</Tabs>

### Executed orders

Query order history for filled and canceled orders:

**Endpoint:** [`POST /api/v4/trade-account/order/history`](/api-reference/spot-trading/query-executed-orders)

For [B2B](/glossary#b2b-partner-account) accounts, canceled orders are not recorded in order history — to obtain canceled-order data, contact support or the assigned account manager.

<Tabs>
  <Tab title="cURL">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    curl -X POST https://whitebit.com/api/v4/trade-account/order/history \
      -H "Content-Type: application/json" \
      -H "X-TXC-APIKEY: YOUR_API_KEY" \
      -H "X-TXC-PAYLOAD: BASE64_PAYLOAD" \
      -H "X-TXC-SIGNATURE: HMAC_SIGNATURE" \
      -d '{"market":"BTC_USDT","request":"/api/v4/trade-account/order/history","nonce":1700000000000}'
    ```
  </Tab>

  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Uses the send_request helper from the "Fetch Trade balance" example.
    history = send_request("/api/v4/trade-account/order/history", {"market": "BTC_USDT"})
    for market, orders in history.items():
        for order in orders[:5]:
            print(f"  {order['side']} {order['amount']} @ {order['price']} — {order.get('type', 'unknown')}")
    ```
  </Tab>
</Tabs>

Order history paginates with the same `limit` (default 50, max 500) and `offset` parameters. Date filtering is limited to a 31-day window between `startDate` and `endDate`, with the earliest reachable date 6 months back — see [Query Executed Orders](/api-reference/spot-trading/query-executed-orders).

### Real-time order updates via WebSocket

To receive an event on order placement, partial fill, or cancellation, subscribe to account streams:

| Channel                                                     | Subscribe method          | Events                                   |
| ----------------------------------------------------------- | ------------------------- | ---------------------------------------- |
| [Orders Pending](/websocket/account-streams/orders-pending) | `ordersPending_subscribe` | Order placed, partially filled, canceled |
| [Deals](/websocket/account-streams/deals)                   | `deals_subscribe`         | Trade executions (fills)                 |

For final order state with aggregate data (fully filled or canceled), see also [Orders Executed](/websocket/account-streams/orders-executed).

The two channels use different `params` shapes: `ordersPending_subscribe` accepts a flat list of markets (`["BTC_USDT"]`), while `deals_subscribe` accepts a list of market lists (`[["BTC_USDT"]]`).

<Tabs>
  <Tab title="Python">
    ```python theme={"theme":{"light":"github-light","dark":"github-dark"}}
    # Add these calls inside monitor_balance() after the
    # balanceSpot_subscribe message. Subscribe to order updates and trade fills.
    await ws.send(json.dumps({
        "id": 2,
        "method": "ordersPending_subscribe",
        "params": ["BTC_USDT"]
    }))
    await ws.send(json.dumps({
        "id": 3,
        "method": "deals_subscribe",
        "params": [["BTC_USDT"]]
    }))
    ```
  </Tab>

  <Tab title="wscat">
    ```bash theme={"theme":{"light":"github-light","dark":"github-dark"}}
    > {"id": 2, "method": "ordersPending_subscribe", "params": ["BTC_USDT"]}
    > {"id": 3, "method": "deals_subscribe", "params": [["BTC_USDT"]]}
    ```
  </Tab>
</Tabs>

## Polling vs WebSocket decision matrix

| Use case                  | Recommended approach    | Reason                                                    |
| ------------------------- | ----------------------- | --------------------------------------------------------- |
| Balance snapshot          | REST                    | One-time check, no persistent connection needed           |
| Live balance changes      | WebSocket               | Push notification on every change                         |
| Deposit/withdrawal status | Webhook + REST fallback | Reliability — webhooks for speed, REST for reconciliation |
| Open order monitoring     | WebSocket               | Real-time state changes (fills, cancellations)            |
| Historical trades         | REST                    | Paginated query over a time range                         |

**General guidance:** Combine WebSocket and REST — WebSocket for the primary real-time flow, REST for startup state hydration and periodic reconciliation.

WebSocket streams do not replay messages that arrived during a disconnection. After a reconnect, re-authorize, re-subscribe, and re-sync state from the REST endpoints — see [WebSocket Quickstart — State recovery after reconnect](/guides/websocket-quickstart#state-recovery-after-reconnect) and [Building a Trading Bot — Auto-resubscription after reconnect](/guides/building-a-trading-bot#auto-resubscription-after-reconnect).

### Recommended polling intervals

When using REST polling, align the interval with the use case and the endpoint rate budget. See [Rate Limits & Error Codes](/api-reference/rate-limits) for per-scope request limits.

| Use case                     | Endpoint                                   | Suggested interval |
| ---------------------------- | ------------------------------------------ | ------------------ |
| Balance dashboard            | `POST /api/v4/trade-account/balance`       | 5–30 seconds       |
| Deposit detection (fallback) | `POST /api/v4/main-account/history`        | 5 minutes          |
| Order history reconciliation | `POST /api/v4/trade-account/order/history` | 30–60 seconds      |

`POST /api/v4/main-account/history` has a much smaller rate budget (200 requests/10 s) than the trade endpoints (12,000 requests/10 s) — keep its polling interval in minutes, not seconds.

Polling loops need failure handling. On HTTP `429`, back off and resume — see [Building a Trading Bot — Rate limit backoff](/guides/building-a-trading-bot#rate-limit-backoff); retried requests need a new, larger `nonce` (see [Authentication](/api-reference/authentication)). On WebSocket, check the `error` field on every response before treating a subscription as active.

## What's next

<CardGroup cols={3}>
  <Card title="Payment Integration" icon="money-bill" href="/guides/payment-integration">
    Full deposit/withdrawal lifecycle, fee calculation, and reconciliation.
  </Card>

  <Card title="WebSocket Quickstart" icon="plug" href="/guides/websocket-quickstart">
    Token-based authorization for private channels and state recovery after reconnect.
  </Card>

  <Card title="Trading Bot" icon="robot" href="/guides/building-a-trading-bot">
    End-to-end automated trading with order management and error handling.
  </Card>
</CardGroup>
