Guides/Real-time Match Tracking

Real-time Match Tracking

Build live match trackers with real-time updates

This guide shows you how to build a real-time match tracker that updates as games progress. We'll cover both polling and webhook approaches.

Approach 1: Polling

Poll the API at regular intervals to get the latest match data:

match-tracker.js
class MatchTracker {
  constructor(apiKey, matchId) {
    this.cito = new CitoAPI(apiKey);
    this.matchId = matchId;
    this.lastUpdate = null;
    this.listeners = [];
  }

  subscribe(callback) {
    this.listeners.push(callback);
    return () => {
      this.listeners = this.listeners.filter(l => l !== callback);
    };
  }

  emit(event, data) {
    this.listeners.forEach(cb => cb(event, data));
  }

  async start() {
    this.interval = setInterval(async () => {
      try {
        const { data: match } = await this.cito.fortnite.matches.get(this.matchId);

        if (this.hasChanged(match)) {
          this.emit('update', match);
          this.lastUpdate = match;
        }
      } catch (error) {
        this.emit('error', error);
      }
    }, 5000); // Poll every 5 seconds
  }

  hasChanged(newData) {
    if (!this.lastUpdate) return true;
    return JSON.stringify(newData) !== JSON.stringify(this.lastUpdate);
  }

  stop() {
    clearInterval(this.interval);
  }
}

// Usage
const tracker = new MatchTracker('sk_live_...', 'match_12345');

tracker.subscribe((event, data) => {
  if (event === 'update') {
    console.log('Match updated:', data);
    updateUI(data);
  }
});

tracker.start();

Approach 2: Webhooks (Recommended)

Use webhooks for instant updates without polling:

// Register webhook for match events
const webhook = await cito.webhooks.create({
  url: 'https://yourapp.com/webhook',
  events: ['match.updated', 'match.ended', 'player.elimination']
});

// Handle incoming webhooks
app.post('/webhook', (req, res) => {
  const { event, data } = req.body;

  // Broadcast to connected clients via WebSocket
  io.emit('match-update', { event, data });

  res.sendStatus(200);
});

React Integration

useMatchTracker.ts
import { useState, useEffect } from 'react';
import { io } from 'socket.io-client';

export function useMatchTracker(matchId: string) {
  const [match, setMatch] = useState(null);
  const [loading, setLoading] = useState(true);
  const [error, setError] = useState(null);

  useEffect(() => {
    // Initial fetch
    fetch(`/api/matches/${matchId}`)
      .then(res => res.json())
      .then(data => {
        setMatch(data);
        setLoading(false);
      })
      .catch(setError);

    // Subscribe to real-time updates
    const socket = io();

    socket.on('match-update', ({ event, data }) => {
      if (data.match_id === matchId) {
        setMatch(prev => ({ ...prev, ...data }));
      }
    });

    return () => socket.disconnect();
  }, [matchId]);

  return { match, loading, error };
}

// Usage in component
function MatchView({ matchId }) {
  const { match, loading, error } = useMatchTracker(matchId);

  if (loading) return <Spinner />;
  if (error) return <Error message={error.message} />;

  return (
    <div>
      <h1>{match.tournament}</h1>
      <div>Game {match.current_game} of {match.total_games}</div>
      <Leaderboard players={match.players} />
    </div>
  );
}

Best Practices

Cache aggressively

Store match data locally and only update changed fields

Use diff updates

Only re-render components that have changed data

Handle reconnection

Implement reconnection logic for WebSocket disconnects