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

# Transactions Stream

> Live WebSocket stream of KOL, Smart Money and Whale transactions with running position, bag percentage and supply percentage per wallet.

The core realtime stream. Every time a tracked wallet buys or sells, you receive a position\_update event with the trade and the wallet running position on that token, including held amount, bag percentage and supply percentage.

All CabalSpy streams share one WebSocket endpoint. You open a single connection, authenticate, then subscribe to as many channels as you like. This page covers the transactions channel.

## Endpoint

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

Pass your API key either as a query parameter or as a Bearer header.

<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: "tx", blockchain: "solana", type: "kol", token: "*",
    }));
  };

  ws.onmessage = (e) => {
    const msg = JSON.parse(e.data);
    if (msg.event === "position_update") {
      const d = msg.data;
      console.log(d.profile.name, d.transaction.action, d.token.symbol, d.position.bag_pct);
    }
  };
  ```

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

  def on_open(ws):
      ws.send(json.dumps({
          "op": "subscribe", "stream": "tx", "blockchain": "solana", "type": "kol", "token": "*",
      }))

  def on_message(ws, message):
      msg = json.loads(message)
      if msg.get("event") == "position_update":
          d = msg["data"]
          print(d["profile"]["name"], d["transaction"]["action"], d["token"]["symbol"])

  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 one of two ways when opening the connection. As a query parameter, `wss://stream.cabalspy.xyz?apiKey=YOUR_KEY`. Or as a header, `Authorization: Bearer YOUR_KEY`. A connection without a valid key is closed immediately with code 1008.

On connect you receive a welcome message listing the available streams and example subscriptions. After that you drive everything with subscribe messages.

## Chains and wallet types

Each chain tracks a different set of wallet categories, and reports values in its own native currency.

| blockchain | Wallet types      | Native currency |
| ---------- | ----------------- | --------------- |
| solana     | kol, smart, whale | SOL             |
| bnb        | kol, smart        | BNB             |
| base       | kol, smart        | ETH             |
| eth        | kol               | ETH             |
| rh         | kol, smart        | ETH             |

Asking for a wallet type a chain does not track is rejected with an error naming the types it does track. On EVM chains, contract addresses are matched case-insensitively.

## Subscribe

To start receiving trades, send a subscribe message after the connection opens. You choose a chain, a wallet type, and optionally one token to narrow to.

```json Subscribe to all KOL trades on Solana theme={null}
{
  "op": "subscribe",
  "stream": "tx",
  "blockchain": "solana",
  "type": "kol",
  "token": "*"
}
```

The token field decides how much you get. Use the value "\*" to receive trades on every token, or a token mint address (Solana) or contract address (BNB, Base, ETH, Robinhood) to receive only trades on that one token.

```json Subscribe to one token only theme={null}
{
  "op": "subscribe",
  "stream": "tx",
  "blockchain": "base",
  "type": "smart",
  "token": "0xYourTokenContractAddress"
}
```

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

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

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

<ParamField body="type" type="string" required>
  Wallet category. See the table above. Solana has kol, smart, whale. BNB, Base and Robinhood have kol, smart. ETH has kol.
</ParamField>

<ParamField body="token" type="string">
  Which token to watch. Either "\*" for all tokens, or a single token mint or contract address to narrow to one token. Defaults to all tokens.
</ParamField>

## Unsubscribe and other operations

To stop a subscription, send the same fields with op unsubscribe.

```json Unsubscribe theme={null}
{
  "op": "unsubscribe",
  "stream": "tx",
  "blockchain": "solana",
  "type": "kol",
  "token": "*"
}
```

To see what you are currently subscribed to, 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" }
```

## Event

Each matching trade delivers a position\_update event.

<ResponseField name="success" type="boolean">
  Always true for stream events.
</ResponseField>

<ResponseField name="channel" type="string">
  The channel that produced the event, for example tx.solana.kol.
</ResponseField>

<ResponseField name="event" type="string">
  position\_update.
</ResponseField>

<ResponseField name="data" type="object">
  <Expandable title="data">
    <ResponseField name="wallet" type="string">
      The trading wallet address.
    </ResponseField>

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

    <ResponseField name="transaction" type="object">
      <Expandable title="transaction">
        <ResponseField name="signature" type="string">
          Transaction signature on Solana, transaction hash on EVM chains.
        </ResponseField>

        <ResponseField name="slot" type="integer">
          Slot of the transaction on Solana, block number on EVM chains, or null.
        </ResponseField>

        <ResponseField name="action" type="string">
          buy or sell.
        </ResponseField>

        <ResponseField name="transaction_type" type="string">
          Same as action.
        </ResponseField>

        <ResponseField name="created_at" type="string">
          Timestamp of the trade, or null.
        </ResponseField>

        <ResponseField name="fee" type="number">
          Raw fee as reported by the source.
        </ResponseField>

        <ResponseField name="fee_native" type="number">
          Fee in native currency, or null.
        </ResponseField>

        <ResponseField name="fee_payer" type="string">
          Fee payer address.
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="token" type="object">
      <Expandable title="token">
        <ResponseField name="mint" type="string">
          Token mint on Solana, contract address on EVM chains.
        </ResponseField>

        <ResponseField name="symbol" type="string">
          Token symbol.
        </ResponseField>

        <ResponseField name="name" type="string">
          Token name.
        </ResponseField>

        <ResponseField name="decimals" type="integer">
          Token decimals, or null.
        </ResponseField>

        <ResponseField name="supply" type="number">
          Total supply.
        </ResponseField>

        <ResponseField name="logo" type="string">
          Token logo URL, or null.
        </ResponseField>
      </Expandable>
    </ResponseField>

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

        <ResponseField name="amount" type="number">
          Trade value in native currency.
        </ResponseField>

        <ResponseField name="amount_usd" type="number">
          Trade value in USD.
        </ResponseField>

        <ResponseField name="price_usd" type="number">
          Native currency price in USD used for the conversion.
        </ResponseField>

        <ResponseField name="peak" type="number">
          Peak invested value on this position (native).
        </ResponseField>

        <ResponseField name="current" type="number">
          Current invested value on this position (native).
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="position" type="object">
      <Expandable title="position">
        <ResponseField name="token_amount" type="number">
          Tokens moved in this trade.
        </ResponseField>

        <ResponseField name="held" type="number">
          Tokens held after this trade.
        </ResponseField>

        <ResponseField name="peak" type="number">
          Peak tokens held.
        </ResponseField>

        <ResponseField name="supply_pct" type="number">
          Percent of supply held after this trade.
        </ResponseField>

        <ResponseField name="bag_pct" type="number">
          Percent of peak holding still held.
        </ResponseField>

        <ResponseField name="prev_supply_pct" type="number">
          Percent of supply held before this trade.
        </ResponseField>

        <ResponseField name="prev_bag_pct" type="number">
          Bag percent before this trade.
        </ResponseField>

        <ResponseField name="delta_supply_pct" type="number">
          Change in supply percent from this trade.
        </ResponseField>

        <ResponseField name="delta_bag_pct" type="number">
          Change in bag percent from this trade.
        </ResponseField>

        <ResponseField name="delta_held" type="integer">
          Change in tokens held from this trade.
        </ResponseField>

        <ResponseField name="used_fallback" type="boolean">
          True when the bag percent was derived from value rather than token counts.
        </ResponseField>
      </Expandable>
    </ResponseField>
  </Expandable>
</ResponseField>

<ResponseExample>
  ```json position_update theme={null}
  {
    "success": true,
    "channel": "tx.solana.kol",
    "event": "position_update",
    "data": {
      "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",
        "active_hours": [13, 14, 15]
      },
      "transaction": {
        "signature": "5xr8abc...",
        "slot": 301948220,
        "action": "buy",
        "transaction_type": "buy",
        "created_at": "2026-07-04T16:25:34Z",
        "fee": 5000,
        "fee_native": 0.000005,
        "fee_payer": "7j7AA3HZR2zEjwAQEKPFh2qucLY4fqZpB9iodf39w8xW"
      },
      "token": {
        "mint": "3Pkrq4MmLvDXyn1fa3sz5MekKRkZkc1iLDcz5AzWpump",
        "symbol": "invisibull",
        "name": "invisibull",
        "decimals": 6,
        "supply": 1000000000,
        "logo": null
      },
      "value": {
        "currency": "SOL",
        "amount": 1.66,
        "amount_usd": 137.44,
        "price_usd": 82.36,
        "peak": 1.66,
        "current": 1.66
      },
      "position": {
        "token_amount": 22113972.03,
        "held": 22113972.03,
        "peak": 22113972.03,
        "supply_pct": 2.2114,
        "bag_pct": 100.0,
        "prev_supply_pct": 0.0,
        "prev_bag_pct": 0.0,
        "delta_supply_pct": 2.2114,
        "delta_bag_pct": 100.0,
        "delta_held": 22113972,
        "used_fallback": false
      }
    },
    "meta": {
      "request_id": "req_5162c7cf7f87",
      "version": "2.0.0",
      "timestamp": "2026-07-06T11:01:57Z"
    }
  }
  ```
</ResponseExample>

## Billing

Each delivered event counts against your plan, the same way a request does. A subscribe to a busy wildcard channel can produce many events, so scope your token filter when you can.

## Heartbeat

The server sends WebSocket pings on an interval and expects your client to answer with pong, which browsers and standard clients do automatically. A connection that stops answering is closed. You can also send op ping at any time to check liveness.
