> ## Documentation Index
> Fetch the complete documentation index at: https://docs.cabalspy.xyz/llms.txt
> Use this file to discover all available pages before exploring further.

# Balance Stream

> Live WebSocket stream of the native balance of tracked wallets, updated on every trade, with previous balance and USD value. Solana, BNB, Base, Ethereum and Robinhood.

Track the native balance of tracked wallets in real time. Whenever a wallet trades, CabalSpy reads its on-chain balance and streams the new value, along with the previous balance so you can show the change.

This stream runs on **Solana, BNB, Base, Ethereum and Robinhood**. The event shape is identical on every chain, only the native currency changes.

On subscribe you get a snapshot of known balances. Subscribing to a specific wallet that is not yet cached triggers an immediate on-demand balance fetch.

<Note>
  On Solana the balance is read from the chain on every trade. On EVM chains it is streamed, which also catches plain transfers and bridge deposits, not just trades. In exchange an EVM update can land a few hundred milliseconds after the trade that caused it.
</Note>

## Endpoint

Connect to `wss://stream.cabalspy.xyz`.

<CodeGroup>
  ```javascript JavaScript theme={null}
  const ws = new WebSocket("wss://stream.cabalspy.xyz?apiKey=YOUR_KEY");

  ws.onopen = () => {
    ws.send(JSON.stringify({
      op: "subscribe", stream: "balance", blockchain: "solana",
      wallet: "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
    }));
  };

  ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.event === "balance_update") {
      const b = msg.data.balance;
      console.log(msg.data.wallet, b.amount, b.currency, b.amount_usd);
    }
  };
  ```

  ```python Python theme={null}
  import json, websocket

  def on_open(ws):
      ws.send(json.dumps({
          "op": "subscribe", "stream": "balance", "blockchain": "solana",
          "wallet": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
      }))

  def on_message(ws, message):
      msg = json.loads(message)
      if msg.get("event") == "balance_update":
          b = msg["data"]["balance"]
          print(msg["data"]["wallet"], b["amount"], b["currency"])

  ws = websocket.WebSocketApp(
      "wss://stream.cabalspy.xyz?apiKey=YOUR_KEY",
      on_open=on_open, on_message=on_message,
  )
  ws.run_forever()
  ```
</CodeGroup>

## Authentication

Provide your API key as a query parameter, wss\://stream.cabalspy.xyz?apiKey=YOUR\_KEY, or as a header, Authorization: Bearer YOUR\_KEY. Invalid keys are closed with code 1008.

## Chains, currencies and wallet types

The balance you receive is always the chain native asset. Decimals and the raw unit differ per chain, so read them from the payload rather than assuming.

| blockchain | Currency | decimals | amount\_raw unit | wallet\_types     |
| ---------- | -------- | -------- | ---------------- | ----------------- |
| solana     | SOL      | 9        | lamports         | kol, smart, whale |
| bnb        | BNB      | 18       | wei              | kol, smart        |
| base       | ETH      | 18       | wei              | kol, smart        |
| eth        | ETH      | 18       | wei              | kol               |
| rh         | ETH      | 18       | wei              | kol, smart        |

Leaving blockchain out defaults to solana, which keeps existing integrations working unchanged.

<Warning>
  amount\_raw is a **string**, not a number. On EVM chains a wei value exceeds what a JSON number can hold exactly, so parse it with BigInt or a decimal library. Use amount for display.
</Warning>

On EVM chains, wallet addresses are matched case-insensitively and are returned lowercased.

## Subscribe

To start receiving balances, send a subscribe message after the connection opens. You choose a chain and which wallet to watch.

```json Watch one wallet on Solana theme={null}
{
  "op": "subscribe",
  "stream": "balance",
  "blockchain": "solana",
  "wallet": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW"
}
```

```json Watch one wallet on Base theme={null}
{
  "op": "subscribe",
  "stream": "balance",
  "blockchain": "base",
  "wallet": "0x7a16ff8270133f063aab6c9977183d9e72835428"
}
```

The wallet field decides what you watch. Use a wallet address to follow one wallet, or the value "\*" to follow every tracked wallet of the chosen types. If you watch a single wallet that CabalSpy has not cached yet, its balance is fetched immediately.

```json Watch all KOL wallets on BNB theme={null}
{
  "op": "subscribe",
  "stream": "balance",
  "blockchain": "bnb",
  "wallet": "*",
  "wallet_types": ["kol"]
}
```

<ParamField body="op" type="string" required>
  The operation. Use subscribe to start.
</ParamField>

<ParamField body="stream" type="string" required>
  Set to balance for this stream.
</ParamField>

<ParamField body="blockchain" type="string">
  Chain. One of solana, bnb, base, eth, rh. Defaults to solana.
</ParamField>

<ParamField body="wallet" type="string" required>
  Which wallet to watch. Either a wallet address for one wallet, or "\*" for all tracked wallets of the chosen types.
</ParamField>

<ParamField body="wallet_types" type="array">
  Which wallet types to include when you use "\*". See the table above. Defaults to every type the chain supports. Ignored when you watch a single wallet.
</ParamField>

## Unsubscribe and other operations

To stop watching a wallet, send the same chain and wallet with op unsubscribe.

```json Unsubscribe theme={null}
{
  "op": "unsubscribe",
  "stream": "balance",
  "blockchain": "solana",
  "wallet": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW"
}
```

To see your active subscriptions, send op subscriptions. To check the connection is alive, send op ping and you get a pong back.

```json List your subscriptions theme={null}
{ "op": "subscriptions", "stream": "balance" }
```

## Events

init on subscribe with a balance snapshot, then balance\_update whenever a watched wallet balance changes.

<ResponseField name="event" type="string">
  init or balance\_update.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="blockchain" type="string">
      The chain. solana, bnb, base, eth or rh.
    </ResponseField>

    <ResponseField name="wallet" type="string">
      Present on balance\_update. The wallet whose balance changed.
    </ResponseField>

    <ResponseField name="wallet_types" type="array">
      Present on init. The wallet types in the snapshot.
    </ResponseField>

    <ResponseField name="profile" type="object">
      Wallet profile (name, image\_url, twitter, telegram, blockchain, currency, type).
    </ResponseField>

    <ResponseField name="balance" type="object">
      <Expandable title="balance">
        <ResponseField name="currency" type="string">
          Native currency of the chain. SOL, BNB or ETH.
        </ResponseField>

        <ResponseField name="amount" type="number">
          Balance in the native currency.
        </ResponseField>

        <ResponseField name="amount_raw" type="string">
          Balance in the smallest unit as a string. Lamports on Solana, wei on EVM chains.
        </ResponseField>

        <ResponseField name="amount_usd" type="number">
          Balance value in USD, or null.
        </ResponseField>

        <ResponseField name="decimals" type="integer">
          9 on Solana, 18 on EVM chains.
        </ResponseField>

        <ResponseField name="symbol" type="string">
          Same as currency. SOL, BNB or ETH.
        </ResponseField>

        <ResponseField name="name" type="string">
          Solana, BNB or Ethereum.
        </ResponseField>

        <ResponseField name="previous_amount" type="number">
          Previous balance in the native currency, on balance\_update. Null in the snapshot.
        </ResponseField>

        <ResponseField name="previous_amount_raw" type="string">
          Previous balance in the smallest unit as a string, on balance\_update.
        </ResponseField>

        <ResponseField name="updated_at" type="integer">
          Update time in milliseconds since epoch.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="balances" type="array">
      Present on init. Each entry has wallet, profile and a balance object without the previous fields.
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>
  ```json balance_update theme={null}
  {
    "success": true,
    "channel": "balance.solana",
    "event": "balance_update",
    "data": {
      "blockchain": "solana",
      "wallet": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW",
      "profile": {
        "name": "Maze",
        "image_url": "https://cabalspy.xyz/images/7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW.png",
        "twitter": "https://x.com/MazeCCC",
        "telegram": "",
        "blockchain": "solana",
        "currency": "SOL",
        "type": "kol"
      },
      "balance": {
        "currency": "SOL",
        "amount": 12.48,
        "amount_raw": "12480000000",
        "amount_usd": 1027.85,
        "decimals": 9,
        "symbol": "SOL",
        "name": "Solana",
        "previous_amount": 14.14,
        "previous_amount_raw": "14140000000",
        "updated_at": 1751799717000
      }
    },
    "meta": {
      "request_id": "req_5162c7cf7f87",
      "version": "2.0.0",
      "timestamp": "2026-07-06T11:01:57Z"
    }
  }
  ```

  ```json balance_update on Base theme={null}
  {
    "success": true,
    "channel": "balance.base",
    "event": "balance_update",
    "data": {
      "blockchain": "base",
      "wallet": "0x7a16ff8270133f063aab6c9977183d9e72835428",
      "profile": {
        "name": "Base Trader",
        "image_url": null,
        "twitter": "",
        "telegram": "",
        "blockchain": "base",
        "currency": "ETH",
        "type": "kol"
      },
      "balance": {
        "currency": "ETH",
        "amount": 3.8175,
        "amount_raw": "3817500000000000000",
        "amount_usd": 9543.75,
        "decimals": 18,
        "symbol": "ETH",
        "name": "Ethereum",
        "previous_amount": 4.3175,
        "previous_amount_raw": "4317500000000000000",
        "updated_at": 1752743524000
      }
    },
    "meta": {
      "request_id": "req_9a1b2c3d4e5f",
      "version": "2.0.0",
      "timestamp": "2026-07-17T09:12:05Z"
    }
  }
  ```
</ResponseExample>

## Billing

Each delivered balance\_update counts against your plan. A wildcard subscription across all wallets can produce many updates, watch specific wallets when you can.

## Heartbeat

The server sends WebSocket pings and expects pong, which standard clients answer automatically. You can also send op ping at any time.
