v2

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: https://live.matchstat.com
Why 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)

EventParametersDescription
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)

EventDataDescription
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-updateMatch update objectReal-time match updates
live-odds-updateOdds update objectReal-time odds updates
live-events-all-updateSport-wide updatesAll 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



Raw JavaScript

JavaScript




Loading...