Skip to main content

Match WebSocket — Roanuz Cricket API

Source: https://www.cricketapi.com/v5/pages/match-websocket

Overview

Receive real-time cricket match updates via Socket.IO WebSocket. This is the primary channel for live ball-by-ball data in Hannibal.

Architecture

  1. Subscribe via HTTP POST to register for WebSocket updates
  2. Connect via Socket.IO to socket.sports.roanuz.com/cricket
  3. Emit connect_to_match with token and match key
  4. Listen for on_match_joined, on_match_update, on_error events
  5. Unsubscribe via HTTP POST when done

Max 20 WebSocket connections per project.

Subscribing twice to the same match does NOT charge twice or send duplicate updates.

Step 1: Subscribe to Match

Endpoint

POST https://api.sports.roanuz.com/v5/cricket/{project_key}/match/{match_key}/subscribe/

Headers

rs-token: <access_token>
Content-Type: application/json

Request Body

{
"method": "web_socket"
}

Code Example (Node.js)

const response = await axios.post(
`https://api.sports.roanuz.com/v5/cricket/${projectKey}/match/${matchKey}/subscribe/`,
{ method: 'web_socket' },
{ headers: { 'rs-token': accessToken, 'Content-Type': 'application/json' } }
);

Step 2: Connect via Socket.IO

Connection Details

  • URL: http://socket.sports.roanuz.com/cricket (NOTE: HTTP, not HTTPS)
  • Path: /v5/websocket
  • Protocol: Socket.IO (not raw WebSocket)

Code Example (Node.js)

const { io } = require('socket.io-client');

const socket = io('http://socket.sports.roanuz.com/cricket', {
path: '/v5/websocket',
transports: ['websocket'],
});

socket.on('connect', () => {
console.log('Connected to Roanuz Socket.IO');

// Join match room
socket.emit('connect_to_match', {
token: accessToken,
match_key: matchKey,
});
});

Step 3: Handle Events

Events

EventDescription
on_match_joinedServer confirms successful subscription to match
on_match_updateReal-time match data update (gzipped)
on_errorError (e.g., match not subscribed)

Data Decompression

Match update data is gzipped. Decompress before parsing:

const { gunzipSync } = require('zlib');

socket.on('on_match_update', (data) => {
// Data may be gzipped Buffer
let parsed;
if (Buffer.isBuffer(data)) {
const decompressed = gunzipSync(data);
parsed = JSON.parse(decompressed.toString());
} else {
parsed = typeof data === 'string' ? JSON.parse(data) : data;
}

// Process match update
console.log('Match update:', parsed);
});

Match Update Data Structure

The on_match_update event contains the full match state including:

  • play.live — Current live score state (runs, wickets, overs)
  • play.related_balls — Recent ball events (object with numeric keys)
  • play.first_batting — Which team batted first
  • play.target — Target score (if second innings)
  • teams.a / teams.b — Team info
  • innings — Innings data
  • toss — Toss information
  • status — Match status
{
"529536": {
"overs": [14, 6],
"batsman": {
"player_key": "c__player__virat_kohli__abc12",
"name": "Virat Kohli",
"runs": 4,
"is_four": true
},
"bowler": {
"player_key": "c__player__jasprit_bumrah__def34",
"name": "Jasprit Bumrah"
},
"runs": 4,
"wicket": null,
"score": { "runs": 156, "wickets": 3, "overs": [14, 6] },
"innings": "a_1",
"key": "529536"
}
}

Step 4: Unsubscribe

Endpoint

POST https://api.sports.roanuz.com/v5/cricket/{project_key}/match/{match_key}/unsubscribe/

Headers

rs-token: <access_token>
Content-Type: application/json

Hannibal Implementation

RoanuzWebSocketManager.ts

Key design:

  • Single Socket.IO connection — multiplexes match subscriptions
  • Exponential backoff reconnection — 1s → 60s, max 10 attempts
  • 30s heartbeat — detects stale connections (2min threshold)
  • Deduplication — tracks processed ball IDs to avoid duplicate events
  • Token refresh — re-subscribes all matches when token changes
  • Gzip decompression — handles compressed on_match_update data

Event Flow

Socket.IO 'on_match_update'
→ gunzipSync(data)
→ JSON.parse
→ Extract play.related_balls
→ For each ball: normalizeRelatedBall()
→ Check dedup (processedBallIds)
→ Emit CricketStreamEvent to handlers
→ ballByBallService processes → AI commentary
→ clientWs broadcasts to frontend

Connection Lifecycle

subscribe(matchId) called
→ ensureSocketConnected()
→ if no socket: connectSocket() (Socket.IO connect)
→ start heartbeat timer
→ subscribeToMatch(matchId)
→ HTTP POST to subscribe endpoint
→ socket.emit('connect_to_match', { token, match_key })
→ Wait for 'on_match_joined' confirmation
→ Register handler in matchSubscriptions map

unsubscribe(matchId, subscriptionId) called
→ Remove handler from matchSubscriptions
→ If no handlers left for match: remove from tracking
→ If no matches left: teardownSocket()