Match WebSocket — Roanuz Cricket API
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
- Subscribe via HTTP POST to register for WebSocket updates
- Connect via Socket.IO to
socket.sports.roanuz.com/cricket - Emit
connect_to_matchwith token and match key - Listen for
on_match_joined,on_match_update,on_errorevents - 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
| Event | Description |
|---|---|
on_match_joined | Server confirms successful subscription to match |
on_match_update | Real-time match data update (gzipped) |
on_error | Error (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 firstplay.target— Target score (if second innings)teams.a/teams.b— Team infoinnings— Innings datatoss— Toss informationstatus— Match status
Related Balls Format
{
"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_updatedata
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()