# Live data over WebSocket

Polling works, but a WebSocket gives you new blocks, fee changes and address activity as they happen, over one connection. You will connect to `wss://api.rarebl.com/ws`, subscribe to channels, handle events, answer the heartbeat and reconnect safely. Every channel, event and limit is in the reference: [WebSocket stream](https://docs.rarebl.com/api/websocket).

## Before you start

- A client that speaks WebSocket: a browser or Node 22+ (global `WebSocket`), or Python with `pip install websockets`.
- Nothing else: the stream is keyless and limited per IP (3 connections, 200 channel subscriptions per connection).
- For events you must not miss while offline, use [webhooks](https://docs.rarebl.com/guides/webhooks) too: a WebSocket has no replay.

## Step 1 — Connect

Open one connection and share it across your app.

```js JavaScript
const ws = new WebSocket('wss://api.rarebl.com/ws');
ws.onopen = () => console.log('connected');
```

```python Python
import asyncio, json, websockets

async def main():
    async with websockets.connect("wss://api.rarebl.com/ws") as ws:
        print("connected")
        # steps 2 and 3 go here

asyncio.run(main())
```

**You should see** the connection open. Nothing is sent until you subscribe.

## Step 2 — Subscribe to channels

Send one `subscribe` message with the channels you want.

```js JavaScript
ws.onopen = () => ws.send(JSON.stringify({
  op: 'subscribe',
  channels: ['live', 'blocks:btc', 'fees:eth', 'address:btc:bc1q7dszvzdj8lk8dd4ssad94m0t2acunaf48j455q'],
}));
```

```python Python
await ws.send(json.dumps({
    "op": "subscribe",
    "channels": ["live", "blocks:btc", "fees:eth", "address:btc:bc1q7dszvzdj8lk8dd4ssad94m0t2acunaf48j455q"],
}))
```

**You should see** a confirmation listing what was accepted and, for anything refused, a `rejected` entry with a reason.

```json
{ "op": "subscribed", "channels": ["live", "blocks:btc", "fees:eth", "address:btc:bc1q7dszvzdj8lk8dd4ssad94m0t2acunaf48j455q"], "rejected": [] }
```

The channels:

| Channel | What you get |
| --- | --- |
| `live` | The heads of every chain (the latest is replayed when you subscribe). |
| `blocks:<chain>` | New blocks of a chain, and reorganisations. |
| `fees:<chain>` | Fee updates. |
| `address:<chain>:<address>` | Activity on an address. On BTC, LTC and BCH the address is **watched automatically**. |
| `tx:<chain>:<txid>` | Progress of one transaction. |
| `token:<chain>:<address>`, `token:<chain>:c:<contract>` | Token transfers of an address or a contract on `eth` or `tron`. |

## Step 3 — Handle events and the heartbeat

Events arrive as `{"channel","type","data","ts"}`. The server sends `{"op":"ping"}` every **20 seconds**: answer `{"op":"pong"}` (any message you send counts as life; after 70 seconds of silence the socket is closed).

```js JavaScript
ws.onmessage = (event) => {
  const m = JSON.parse(event.data);
  if (m.op === 'ping') return ws.send(JSON.stringify({ op: 'pong' }));
  if (m.op === 'error') return console.error(m.code, m.message);
  if (m.channel) queue.push(m);                    // hand work to a queue: never block this handler
};
```

```python Python
async for raw in ws:
    m = json.loads(raw)
    if m.get("op") == "ping":
        await ws.send(json.dumps({"op": "pong"}))
    elif m.get("op") == "error":
        print(m["code"], m["message"])
    elif "channel" in m:
        queue.put_nowait(m)                         # hand work to a queue: never block this loop
```

**You should see** events such as a new block (example values below; the shape of `data` follows [Data types](https://docs.rarebl.com/data-types) and the webhook events in [Webhooks](https://docs.rarebl.com/guides/webhooks)). Event types: `block.new`, `block.reorged`, `address.activity`, `tx.confirmations`, `tx.final`, `tx.dropped`, `tx.replaced`, `token.transfer`, `live` and `fees`.

```json
{ "channel": "blocks:btc", "type": "block.new", "data": { "height": 975870, "hash": "…" }, "ts": "2026-11-15T01:09:33Z" }
```

To stop a channel, send `{"op":"unsubscribe","channels":["fees:eth"]}`.

## Step 4 — Reconnect with back-off

Subscriptions do not survive a dropped connection, so resubscribe on every reconnect. Back off, and add jitter if you run many clients.

```js JavaScript
const CHANNELS = ['live', 'blocks:btc'];
let delay = 1000;

function connect() {
  const ws = new WebSocket('wss://api.rarebl.com/ws');
  ws.onopen = () => { delay = 1000; ws.send(JSON.stringify({ op: 'subscribe', channels: CHANNELS })); };
  ws.onmessage = onMessage;
  ws.onclose = () => { setTimeout(connect, delay + Math.random() * 500); delay = Math.min(delay * 2, 30000); };
}
connect();
```

```python Python
async def run():
    delay = 1
    while True:
        try:
            async with websockets.connect("wss://api.rarebl.com/ws") as ws:
                delay = 1
                await ws.send(json.dumps({"op": "subscribe", "channels": CHANNELS}))
                async for raw in ws:
                    handle(json.loads(raw), ws)
        except (OSError, websockets.ConnectionClosed):
            await asyncio.sleep(delay)
            delay = min(delay * 2, 30)
```

**You should see** the stream resume after a network blip. While you were disconnected you may have missed events: catch up with [Live stats](https://docs.rarebl.com/api/stats/live), [Block](https://docs.rarebl.com/api/block/get) or [Transaction status](https://docs.rarebl.com/api/tx/status).

## Step 5 — Stay within the limits

| Limit | Value |
| --- | --- |
| Connections | 3 per IP. Share one connection across your app; the 4th is closed (code 1008). |
| Subscriptions | 200 channels per connection. |
| Slow clients | A client with 256 unacknowledged messages is closed (code 1013). Do not block the handler. |
| Client messages | More than 60 in 10 seconds closes the socket (1008); incoming frames are capped at 16 kB. |
| Heartbeat | The server pings every 20 s; answer `{"op":"pong"}`. |
| Watches | Address channels on BTC, LTC and BCH watch the address, which counts against 2,000 addresses per day ([Rate limits](https://docs.rarebl.com/rate-limits)). |

## WebSocket or webhook?

| | WebSocket | Webhook |
| --- | --- | --- |
| Direction | You connect to Rarebl | Rarebl calls your server |
| Best for | Dashboards, wallets in a browser, live fees | Servers that must not miss an event |
| Delivery guarantee | None: events while disconnected are missed | Retried for 24 hours, up to 20 failures |
| Setup | One connection | A public HTTPS endpoint |

## Common errors

| Symptom | What it means | What to do |
| --- | --- | --- |
| `{"op":"error","code","message"}` | The server refused a message. | Match on `code`, fix the message. |
| `rejected` entries in `subscribed` | A channel was invalid or over the limit. | Check the channel name and your count. |
| Closed with 1008 | A fourth connection, or too many messages. | Reuse one connection; slow down. |
| Closed with 1013 | You were too slow to read. | Process events on a queue and reconnect. |
| Closed after 70 s silent | The pong was missing. | Answer every `ping` with `pong`. |

## Next steps

- Reference: [WebSocket stream](https://docs.rarebl.com/api/websocket).
- Server-to-server delivery with retries: [Webhooks](https://docs.rarebl.com/guides/webhooks).
- Poll instead: [Live stats](https://docs.rarebl.com/api/stats/live) and [Market data and fees](https://docs.rarebl.com/guides/market-data).
