Real-time Integration
Socket.IO Integration
Real-time push updates are available via Socket.IO. Subscribe to live events, live odds, and sport-wide feeds without polling. This page provides complete integration examples for all major frameworks.
Token Authentication Required: Socket.IO connections now require a special token obtained from the
/ws-token endpoint. This token is only available to MEGA plan subscribers. See the Token Authentication section below.
Socket.IO URL:
Why Token: RapidAPI doesn't natively support WebSocket authentication, so we use this token-based system to verify MEGA plan access.
https://live.matchstat.comWhy Token: RapidAPI doesn't natively support WebSocket authentication, so we use this token-based system to verify MEGA plan access.
Installation
npm
npm install socket.io-client
yarn
yarn add socket.io-client
Connection Singleton with Token Management
Use this singleton pattern with automatic token refresh to ensure only one Socket.IO connection exists across your application:
TypeScript
"use client";
import { MS_LIVE_DOMAIN } from "@/_constant";
import { io, Socket } from "socket.io-client";
let socket: Socket | null = null;
let refreshToken = false;
async function getWsToken(): Promise {
const response = await fetch(
'https://tennis-api-atp-wta-itf.p.rapidapi.com/tennis/v2/extend/api/ws-token',
{
method: 'GET',
headers: {
'Content-Type': 'application/json',
'x-rapidapi-host': 'tennis-api-atp-wta-itf.p.rapidapi.com',
'x-rapidapi-key': YOUR_RAPIDAPI_KEY
}
}
);
const data = await response.json();
if (data.token) {
return data.token;
}
throw new Error('Failed to get WebSocket token');
}
export async function tennisLiveApiSocketConn(): Promise {
if (!socket) {
const token = await getWsToken();
socket = io(MS_LIVE_DOMAIN, {
auth: {
token,
},
transports: ["websocket"],
reconnection: true,
autoConnect: true,
});
socket.on("connect", () => {
// console.log("Socket connected:", socket?.id);
});
socket.on("connect_error", async (err: any) => {
if (err.message === "TOKEN_EXPIRED" && !refreshToken) {
try {
refreshToken = true;
const token = await getWsToken();
if (!socket) return;
socket.auth = { token };
socket.connect();
} finally {
refreshToken = false;
}
}
});
socket.on("disconnect", (reason) => {
// console.log("Socket disconnected:", reason);
});
}
return socket;
}
Constants File: Create a constants file to store your configuration:
// @/_constant.ts export const MS_LIVE_DOMAIN = 'YOUR_SOCKET_URL'; // e.g., https://live.matchstat.com export const ODDS_SOCKET_KEY = 'YOUR_RAPIDAPI_KEY';
MEGA Plan Required: WebSocket token authentication is only available to MEGA plan subscribers. Free/Basic plans will receive an error when trying to access the ws-token endpoint.
Token Authentication (MEGA Plan Only)
Socket.IO connections require a special token obtained from the WebSocket token endpoint. This token is only available to MEGA plan subscribers.
Get WebSocket Token
cURL
curl --request GET \
--url https://tennis-api-atp-wta-itf.p.rapidapi.com/tennis/v2/extend/api/ws-token \
--header 'Content-Type: application/json' \
--header 'x-rapidapi-host: tennis-api-atp-wta-itf.p.rapidapi.com' \
--header 'x-rapidapi-key: YOUR_RAPIDAPI_KEY'
Response Examples
If NOT MEGA plan user:
JSON
{
"success": false,
"message": "WebSocket access requires MEGA plan & Your plan is - XYZ.",
"token": null
}
If MEGA plan user:
JSON
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIs..."
}
Implementation Note: Call the
/ws-token endpoint once inside your Socket skeleton to get the token. Then use this token (not your API key) when establishing the WebSocket connection.
Client & Server Events
Client Emit Events (send to server)
| Event | Parameters | Description |
|---|---|---|
join-event | { eventId: string } | Join a live match room |
leave-event | { eventId: string } | Leave a live match room |
join-live-events-all | { sportId: number } | Join all live events for a sport |
leave-live-events-all | { sportId: number } | Leave all live events for a sport |
Server On Events (receive from server)
| Event | Data | Description |
|---|---|---|
live-event-joined | { success: boolean, message: string } | Confirmation after joining event |
live-event-left | { success: boolean, message: string } | Confirmation after leaving event |
live-events-all-joined | { success: boolean, message: string } | Confirmation after joining sport feed |
live-events-all-left | { success: boolean, message: string } | Confirmation after leaving sport feed |
live-event-update | Match update object | Real-time match updates |
live-odds-update | Odds update object | Real-time odds updates |
live-events-all-update | Sport-wide updates | All live events for sport |
Best Practices
- Singleton Connection: Use a singleton pattern to ensure only one Socket.IO connection exists
- Cleanup Listeners: Always remove event listeners when components unmount to prevent memory leaks
- Leave Rooms: Explicitly leave rooms when done to reduce server load
- Error Handling: Handle connection errors and implement reconnection logic
- Rate Limiting: Don't join/leave rooms rapidly; batch operations when possible
Mega Plan Required: Socket.IO real-time updates are only available with the Mega Plan subscription. Standard plans can use REST polling instead.
Sport Support: Currently supports Tennis (sportId: 5) with more sports coming soon.
React / Next.js Integration with Enhanced Token Management
Using the TypeScript Socket Singleton
React Hook
import { useEffect, useState } from 'react';
import { tennisLiveApiSocketConn } from './libs/odds-api-socket.lib';
export const useLiveMatch = (eventId: string) => {
const [matchData, setMatchData] = useState(null);
const [error, setError] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
let socket: any;
const connectSocket = async () => {
try {
setLoading(true);
socket = await tennisLiveApiSocketConn();
// Join specific event room
socket.emit('join-event', { eventId });
// Listen for updates
socket.on('live-event-update', (data: any) => {
setMatchData(data);
setLoading(false);
});
socket.on('live-event-joined', (response: any) => {
if (!response.success) {
setError(response.message);
setLoading(false);
}
});
socket.on('live-event-odds', (oddsData: any) => {
// Handle odds updates
setMatchData(prev => ({
...prev,
odds: oddsData
}));
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Connection failed');
setLoading(false);
}
};
if (eventId) {
connectSocket();
}
return () => {
if (socket) {
socket.emit('leave-event', { eventId });
socket.off('live-event-update');
socket.off('live-event-joined');
socket.off('live-event-odds');
}
};
}, [eventId]);
return { matchData, error, loading };
};
// Usage in component
const LiveMatchComponent = ({ eventId }: { eventId: string }) => {
const { matchData, error, loading } = useLiveMatch(eventId);
if (loading) return Connecting to live match...;
if (error) return Error: {error};
if (!matchData) return No match data available;
return (
{matchData.event?.name}
Score: {matchData.score}
Status: {matchData.status}
{matchData.odds && (
Live Odds
Player 1: {matchData.odds.player1}
Player 2: {matchData.odds.player2}
)}
);
};
Multiple Events Subscription
React Hook
import { useEffect, useState } from 'react';
import { tennisLiveApiSocketConn } from './libs/odds-api-socket.lib';
export const useLiveEvents = (sport: string = 'atp') => {
const [events, setEvents] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
let socket: any;
const connectSocket = async () => {
try {
socket = await tennisLiveApiSocketConn();
// Join all events for a sport
socket.emit('join-sport-events', { sport });
socket.on('live-events-all-update', (data: any) => {
setEvents(data.events || []);
});
socket.on('sport-events-joined', (response: any) => {
if (!response.success) {
setError(response.message);
}
});
} catch (err) {
setError(err instanceof Error ? err.message : 'Connection failed');
}
};
connectSocket();
return () => {
if (socket) {
socket.emit('leave-sport-events', { sport });
socket.off('live-events-all-update');
socket.off('sport-events-joined');
}
};
}, [sport]);
return { events, error };
};
Angular Integration
Angular Service
// socket.service.ts
import { Injectable } from '@angular/core';
import { io, Socket } from 'socket.io-client';
import { BehaviorSubject } from 'rxjs';
import { HttpClient } from '@angular/common/http';
interface WsTokenResponse {
success: boolean;
token: string | null;
message?: string;
}
@Injectable({ providedIn: 'root' })
export class SocketService {
private socket: Socket | null = null;
private matchData$ = new BehaviorSubject(null);
private error$ = new BehaviorSubject(null);
constructor(private http: HttpClient) {}
async connect(apiKey: string): Promise {
// Get WebSocket token (MEGA plan only)
const tokenResponse = await this.http.get(
'https://tennis-api-atp-wta-itf.p.rapidapi.com/tennis/v2/extend/api/ws-token',
{
headers: {
'x-rapidapi-host': 'tennis-api-atp-wta-itf.p.rapidapi.com',
'x-rapidapi-key': apiKey
}
}
).toPromise();
if (!tokenResponse?.success || !tokenResponse.token) {
this.error$.next('WebSocket access requires MEGA plan subscription');
return false;
}
const SOCKET_URL = 'https://live.matchstat.com';
this.socket = io(SOCKET_URL, {
auth: { token: tokenResponse.token },
transports: ['websocket']
});
return true;
}
joinEvent(eventId: string) {
if (!this.socket) return;
this.socket.emit('join-event', { eventId });
this.socket.on('live-event-update', (data) => {
this.matchData$.next(data);
});
}
leaveEvent(eventId: string) {
if (!this.socket) return;
this.socket.emit('leave-event', { eventId });
}
getMatchData() {
return this.matchData$.asObservable();
}
getError() {
return this.error$.asObservable();
}
}
Angular Component
// live-match.component.ts
import { Component, OnDestroy, OnInit } from '@angular/core';
import { SocketService } from './socket.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-live-match',
template: `
{{ error }}
{{ matchData.name }} - {{ matchData.score }}
`
})
export class LiveMatchComponent implements OnInit, OnDestroy {
matchData: any;
error: string | null = null;
private subscriptions: Subscription[] = [];
constructor(private socketService: SocketService) {}
async ngOnInit() {
// Connect with token (MEGA plan required)
const connected = await this.socketService.connect('YOUR_RAPIDAPI_KEY');
if (!connected) {
this.error = 'WebSocket access requires MEGA plan subscription';
return;
}
this.subscriptions.push(
this.socketService.getMatchData()
.subscribe(data => this.matchData = data),
this.socketService.getError()
.subscribe(error => this.error = error)
);
this.socketService.joinEvent('EVENT_ID');
}
ngOnDestroy() {
this.subscriptions.forEach(sub => sub.unsubscribe());
this.socketService.leaveEvent('EVENT_ID');
}
}
Vue 3 Integration
Vue 3 Composable
// composables/useLiveMatch.js
import { ref, onMounted, onUnmounted } from 'vue';
import { tennisApiLiveSocketConn } from '../socket';
// Get WebSocket token (MEGA plan only)
const getWsToken = async (apiKey) => {
const response = await fetch(
'https://tennis-api-atp-wta-itf.p.rapidapi.com/tennis/v2/extend/api/ws-token',
{
headers: {
'x-rapidapi-host': 'tennis-api-atp-wta-itf.p.rapidapi.com',
'x-rapidapi-key': apiKey
}
}
);
const data = await response.json();
return data.success ? data.token : null;
};
export function useLiveMatch(eventId, apiKey) {
const matchData = ref(null);
const error = ref(null);
let socket;
onMounted(async () => {
const token = await getWsToken(apiKey);
if (!token) {
error.value = 'WebSocket access requires MEGA plan subscription';
return;
}
socket = tennisApiLiveSocketConn(token);
socket.emit('join-event', { eventId });
socket.on('live-event-update', (data) => {
matchData.value = data;
});
});
onUnmounted(() => {
if (socket) {
socket.emit('leave-event', { eventId });
socket.off('live-event-update');
}
});
return { matchData, error };
}
Vue Component
{{ error }}
{{ matchData.name }} - {{ matchData.score }}
Raw JavaScript
JavaScript
Loading...