Skip to main content

WebSocket API

Real-time round updates via WebSocket connection.


Connection

wss://rng.dev/ws/rounds

Compression: The server supports permessage-deflate compression (60-80% bandwidth reduction). Most WebSocket clients negotiate this automatically. Latency impact is negligible (<1ms).

On Connect: The server immediately sends the current round when you connect. You don't need to wait up to 60 seconds for the next round.


Quick Start

JavaScript

const ws = new WebSocket('wss://rng.dev/ws/rounds');

ws.onopen = () => {
console.log('Connected to beacon');
};

ws.onmessage = (event) => {
const round = JSON.parse(event.data);
console.log(`Round ${round.round}: die = ${round.die_value}`);
};

ws.onclose = () => {
console.log('Disconnected');
};

ws.onerror = (error) => {
console.error('WebSocket error:', error);
};

Python

import asyncio
import websockets
import json

async def listen():
uri = "wss://rng.dev/ws/rounds"
async with websockets.connect(uri) as websocket:
async for message in websocket:
round_data = json.loads(message)
print(f"Round {round_data['round']}: die = {round_data['die_value']}")

asyncio.run(listen())

Message Format

Round Update

Sent every 60 seconds when a new round is generated:

{
"type": "round",
"round": 12345678,
"output_hash": "a3f2e8c9d1b4a5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b9c0",
"die_value": 4,
"generated_at": "2026-03-09T15:00:00Z",
"status": "complete",
"sources_available": 6,
"sources_total": 6,
"inputs": {
"algorand": "34567890:QWERTY...XYZ",
"avalanche": "12345678:0xabc123...",
"bitcoin": "00000000000000000002a7c4...3f:831245",
"cardano": "8765432:def456...",
"ethereum": "0x8a3f2e...b9:19234567",
"solana": "245678901:5eykt4Uy...Gno"
}
}

The inputs field contains the exact strings used to generate output_hash, enabling real-time verification. See Verification Guide for details.

Heartbeat

Sent every 30 seconds to keep connection alive:

{
"type": "heartbeat",
"timestamp": "2026-03-09T15:00:30Z"
}

Error

Sent when an error occurs:

{
"type": "error",
"code": "GENERATION_FAILED",
"message": "Failed to generate round: insufficient sources"
}

Connection Management

Automatic Reconnection

Implement exponential backoff for reconnection:

class BeaconClient {
constructor() {
this.reconnectDelay = 1000;
this.maxReconnectDelay = 30000;
this.connect();
}

connect() {
this.ws = new WebSocket('wss://rng.dev/ws/rounds');

this.ws.onopen = () => {
console.log('Connected');
this.reconnectDelay = 1000; // Reset on successful connection
};

this.ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'round') {
this.onRound(data);
}
};

this.ws.onclose = () => {
console.log(`Reconnecting in ${this.reconnectDelay}ms...`);
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(
this.reconnectDelay * 2,
this.maxReconnectDelay
);
};
}

onRound(round) {
// Override this method
console.log('New round:', round);
}
}

Heartbeat Handling

If no heartbeat received in 60 seconds, consider the connection stale:

let lastHeartbeat = Date.now();

ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'heartbeat') {
lastHeartbeat = Date.now();
}
};

setInterval(() => {
if (Date.now() - lastHeartbeat > 60000) {
console.log('Connection stale, reconnecting...');
ws.close();
}
}, 10000);

Rate Limits

LimitValue
Connections per IP10
Messages per minuteN/A (server-initiated only)

Exceeding connection limits will result in connection rejection.


Best Practices

Do

  • Implement automatic reconnection with backoff
  • Handle heartbeat messages to detect stale connections
  • Parse and validate incoming JSON
  • Close connections cleanly when done

Don't

  • Open multiple connections from the same client
  • Ignore connection errors
  • Assume messages arrive in order (they will, but verify)
  • Keep connections open when not needed

Example: React Hook

import { useState, useEffect, useCallback } from 'react';

interface Round {
round: number;
output_hash: string;
die_value: number;
generated_at: string;
status: string;
sources_available: number;
sources_total: number;
inputs: Record<string, string>;
}

export function useBeaconRounds() {
const [currentRound, setCurrentRound] = useState<Round | null>(null);
const [connected, setConnected] = useState(false);

useEffect(() => {
let ws: WebSocket;
let reconnectTimeout: NodeJS.Timeout;

const connect = () => {
ws = new WebSocket('wss://rng.dev/ws/rounds');

ws.onopen = () => setConnected(true);
ws.onclose = () => {
setConnected(false);
reconnectTimeout = setTimeout(connect, 3000);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.type === 'round') {
setCurrentRound(data);
}
};
};

connect();

return () => {
clearTimeout(reconnectTimeout);
ws?.close();
};
}, []);

return { currentRound, connected };
}

Debugging

Test Connection

# Using websocat
websocat wss://rng.dev/ws/rounds

# Using wscat
npx wscat -c wss://rng.dev/ws/rounds

Common Issues

IssueCauseSolution
Connection refused (1008)Too many connections from your IP (max 10)Close unused connections
No messages after connectShould receive current round immediatelyCheck connection is open
Parse errorInvalid JSONReport bug to support

Next Steps