Live data over WebSocket
Step by step: connect to wss://api.rarebl.com/ws, subscribe, handle events and pings, reconnect and stay within the limits.
# 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).