Skip to main content
Connection lifecycle, authentication, heartbeat, and reconnection patterns for XOXNO WebSocket APIs.

Endpoints

The Relayer exposes two WebSocket endpoints:
EndpointModeDescription
wss://relayer.xoxno.com/wsFull-duplexSubscribe to topics, broadcast transactions, relay transactions
wss://relayer.xoxno.com/ws/statsRead-onlyPushes networkStats at adaptive intervals. No subscribe required.
The /ws/stats endpoint operates independently. Connect to both when your application needs subscription-based topics alongside the network stats stream.

Connection lifecycle

Client                              Server
  |                                   |
  |──── WebSocket handshake ─────────>|
  |<──── 101 Switching Protocols ─────|
  |                                   |
  |──── subscribe { gasStats } ──────>|
  |<──── { status: "ok" } ───────────|
  |                                   |
  |<──── gasStats event ─────────────|
  |<──── gasStats event ─────────────|
  |                                   |
  |──── relay { tx } ────────────────>|
  |<──── { hashes: [...] } ──────────|
  |                                   |
  |──── unsubscribe { gasStats } ────>|
  |<──── { status: "ok" } ───────────|
  |                                   |
  |──── close ───────────────────────>|
  |<──── close ──────────────────────|
  1. Connect: Open a WebSocket connection to either endpoint.
  2. Subscribe: Send subscribe actions to register for topics (/ws/stats subscribes automatically).
  3. Receive events: The server pushes JSON events for each subscribed topic.
  4. Send actions: Submit broadcast or relay actions to process transactions.
  5. Unsubscribe: Remove subscriptions you no longer need.
  6. Disconnect: Close the connection cleanly.

Authentication

Neither WebSocket endpoint requires authentication. The server treats all connections equally.

Message format

All messages use JSON encoding. Text frames are the standard transport. The server also accepts binary frames for legacy JSON payloads and protobuf batch broadcast.
  • Maximum message size: 4 MB
  • Encoding: UTF-8 JSON
Client-to-server messages include an action field. Server-to-client messages include either a type field (for events) or an action + status field (for action responses).

Heartbeat

The server sends a WebSocket ping frame every 30 seconds. Your client must respond with a pong frame. Most WebSocket libraries handle this automatically. If the server receives no pong within the next ping interval, it closes the connection.

Connection limits

ParameterValue
Max topics per connection64
Rate limit (control messages)64 per 5-second window
Max message size4 MB
Ping/pong heartbeat interval30 seconds
Sending more than 64 control messages within a 5-second window triggers a rate-limit error. Both subscribe and unsubscribe actions count toward this limit.

Reconnection strategy

Use exponential backoff with a cap:
AttemptDelay
11 second
22 seconds
34 seconds
48 seconds
516 seconds
6+30 seconds (cap)
After reconnecting, re-subscribe to all topics. The server does not persist subscriptions across connections.

Connection examples

const ws = new WebSocket('wss://relayer.xoxno.com/ws');

ws.onopen = () => {
  console.log('Connected');
  ws.send(JSON.stringify({ action: 'subscribe', topic: 'gasStats' }));
};

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  console.log('Received:', msg.type || msg.action);
};

ws.onclose = () => {
  console.log('Disconnected: implement reconnection here');
};
import asyncio
import json
import websockets

async def connect():
    uri = "wss://relayer.xoxno.com/ws"
    async with websockets.connect(uri) as ws:
        await ws.send(json.dumps({
            "action": "subscribe",
            "topic": "gasStats"
        }))

        async for message in ws:
            msg = json.loads(message)
            print(f"Received: {msg.get('type') or msg.get('action')}")

asyncio.run(connect())
use futures_util::{SinkExt, StreamExt};
use serde_json::json;
use tokio_tungstenite::connect_async;

#[tokio::main]
async fn main() {
    let (mut ws, _) = connect_async("wss://relayer.xoxno.com/ws")
        .await
        .expect("failed to connect");

    let subscribe = json!({
        "action": "subscribe",
        "topic": "gasStats"
    });

    ws.send(subscribe.to_string().into()).await.unwrap();

    while let Some(Ok(msg)) = ws.next().await {
        if let Ok(text) = msg.into_text() {
            println!("Received: {text}");
        }
    }
}
# Install: cargo install websocat
# Connect and subscribe:
echo '{"action":"subscribe","topic":"gasStats"}' | \
  websocat wss://relayer.xoxno.com/ws

Read-only stats stream

The /ws/stats endpoint requires no subscribe action. Data arrives as soon as you connect:
const ws = new WebSocket('wss://relayer.xoxno.com/ws/stats');

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  // msg.type === 'networkStats'
  const { network, mempool } = msg.data;
  console.log(`TPS: ${network.currentTps} | Mempool: ${mempool.estimatedQueue}`);
};

subscribe

Subscribe to real-time topics: gas stats, network stats, account deltas, and tx status.

broadcast

Submit pre-signed transactions for P2P broadcast via WebSocket.

gasStats

Per-shard gas price and PPU statistics, every 50ms.

networkStats

Full network snapshot: TPS, blocks, mempool, validators, health.