Skip to content

Real-Time Event Streaming API

Access Required

This API endpoint is only available to AllStar and custom plan subscribers. It is not included with basic subscription tiers. Contact support to get access.

Beta Feature

This streaming API is currently in beta. API call patterns, response formats, and functionality may change.

Our Streaming API provides real-time updates for Event objects through WebSocket connections. Instead of polling our REST endpoints, you can maintain a persistent connection to receive instant notifications when events change. This is ideal for applications that need immediate updates with minimal delay.

We use Pusher Protocol for WebSocket communication. While you can connect using any WebSocket library, we strongly recommend using any Pusher Client Library (ex: Javascript, Python)

How It Works

The streaming process involves two steps:

  1. Get Connection Details: Make a request to /v2/stream/events to receive:

    • WebSocket authentication credentials
    • WebSocket URL/channel info
    • Initial snapshot of current data
  2. Connect and Stream: Use the provided details to connect via Pusher (or another WebSocket library) and receive real-time eventID notifications for changed events

Rate Limits

Your API key will have limits on concurrent streams.

Available Feeds

Subscribe to different feeds using the feed query parameter:

FeedDescriptionRequired Parameters
events:liveAll events currently in progress (started but not finished)None
events:upcomingUpcoming events with available odds for a specific leagueleagueID
events:byidUpdates for a single specific eventeventID

Feeds

The number of supported feeds will increase over time. Please reach out if you have a use case which can’t be covered by these feeds.

Quick Start Example

Here’s the minimal code to connect to live events:

js
const axios = require("axios");
const Pusher = require("pusher-js");

const STREAM_FEED = "events:live"; // ex: events:upcoming, events:byid, events:live
const API_BASE_URL = "https://api.sportsgameodds.com/v2";
const API_KEY = "YOUR API KEY";

const run = async () => {
  // Initialize a data structure where we'll save the event data
  const EVENTS = new Map();

  // Call this endpoint to get initial data and connection parameters
  const streamInfo = await axios.get(`${API_BASE_URL}/stream/events`, { headers: { "x-api-key": API_KEY }, params: { feed: STREAM_FEED } }).then((r) => r?.data);

  // Seed initial data
  streamInfo.data.forEach((event) => EVENTS.set(event?.eventID, event));

  // Connect to WebSocket server
  const pusher = new Pusher(streamInfo.pusherKey, streamInfo.pusherOptions);
  pusher.subscribe(streamInfo.channel).bind("data", async (changedEvents) => {
    // Get the eventIDs that changed
    const eventIDs = changedEvents.map(({ eventID }) => eventID).join(",");

    // Get the full event data for the changed events
    const eventDataResponse = await axios.get(`${API_BASE_URL}/events`, { headers: { "x-api-key": API_KEY }, params: { eventIDs } }).then((r) => r?.data);

    // Update our data with the full event data
    eventDataResponse.data.forEach((newEventData) => EVENTS.set(newEventData?.eventID, newEventData));
  });

  // Use pusher.disconnect to disconnect from the WebSocket server
  process.on("SIGINT", pusher.disconnect);
};
run();
python
import requests
import pusherclient
import json

API_KEY = 'YOUR_API_KEY'
API_BASE_URL = 'https://api.sportsgameodds.com/v2'

def handle_update(data):
    try:
        changed_events = json.loads(data)
        event_ids = ','.join([e['eventID'] for e in changed_events])

        # Fetch updated event data
        response = requests.get(
            f"{API_BASE_URL}/events",
            headers={'x-api-key': API_KEY},
            params={'eventIDs': event_ids}
        )

        for event in response.json()['data']:
            print(f"Updated: {event['eventID']}")

    except Exception as e:
        print(f"Error processing update: {e}")

def connect_to_live_events():
    # Get connection details
    response = requests.get(
        f"{API_BASE_URL}/stream/events",
        headers={'x-api-key': API_KEY},
        params={'feed': 'events:live'}
    )

    stream_info = response.json()
    print(f"Connected with {len(stream_info['data'])} initial events")

    # Connect to Pusher (Note: private channels require custom auth in Python)
    pusher_key = stream_info['pusherKey']
    channel_name = stream_info['channel']

    pusher = pusherclient.Pusher(pusher_key)
    channel = pusher.subscribe(channel_name)
    channel.bind('data', handle_update)

    pusher.connection.bind('pusher:connection_established',
                          lambda data: print("Connected to stream"))

if __name__ == "__main__":
    connect_to_live_events()
ruby
require 'net/http'
require 'json'
require 'pusher-client'
require 'uri'

STREAM_FEED = "events:live" # ex: events:upcoming, events:byid, events:live
API_BASE_URL = "https://api.sportsgameodds.com/v2"
API_KEY = "API_KEY_GOES_HERE"

def convert_ruby_options(standard_options, api_key)
  ruby_client_options = {}

  ruby_client_options[:ws_host] = standard_options['wsHost'] if standard_options['wsHost']
  ruby_client_options[:ws_port] = standard_options['wsPort'] if standard_options['wsPort']
  ruby_client_options[:wss_port] = standard_options['wssPort'] if standard_options['wssPort']
  ruby_client_options[:ws_path] = standard_options['wsPath'] if standard_options['wsPath']
  ruby_client_options[:ws_host] = "ws-#{standard_options['cluster']}.pusher.com" if standard_options['cluster'] && !ruby_client_options[:ws_host]
  ruby_client_options[:secure] = true

  if (standard_options.dig('channelAuthorization', 'endpoint'))
    ruby_client_options[:auth_method] = ->(socket_id, channel) {
      headers = (standard_options.dig('channelAuthorization', 'headers') || {}).merge('Content-Type' => 'application/json')
      params = (standard_options.dig('channelAuthorization', 'params') || {}).merge('socket_id' => socket_id, 'channel_name' => channel.name)
      res = Net::HTTP.post(URI(standard_options.dig('channelAuthorization', 'endpoint')), params.to_json, headers)
      res.is_a?(Net::HTTPSuccess) ? JSON.parse(res.body)['auth'] : raise("Auth failed: HTTP #{res.code} - #{res.body}")
    }
  end

  ruby_client_options
end

def api_get(path, params = {})
  uri = URI("#{API_BASE_URL}#{path}")
  uri.query = URI.encode_www_form(params) unless params.empty?
  res = Net::HTTP.get_response(uri, {'x-api-key' => API_KEY})
  raise "HTTP #{res.code}: #{res.message}" unless res.is_a?(Net::HTTPSuccess)

  JSON.parse(res.body)
end

def run
  events = {}

  # Get initial data and connection parameters
  stream_info = api_get("/stream/events", feed: STREAM_FEED)
  stream_info['data'].each { |e| events[e['eventID']] = e }

  # Convert the standard options to Ruby SDK options
  connect_options = convert_ruby_options(stream_info['pusherOptions'], API_KEY)

  # Initialize the client
  client = PusherClient::Socket.new(stream_info['pusherKey'], connect_options)

  # Set up other event handlers as needed
  client.bind('pusher:connection_established') { puts "Connected" }
  client.bind('pusher:subscription_succeeded') { |_| puts "Subscribed to: #{stream_info['channel']}" }
  client.bind('pusher:subscription_error') { |data| puts "Error: #{data}" }

  # Subscribe to and handle data events in the channel
  channel = client.subscribe(stream_info['channel'], stream_info['user'])
  channel.bind('data') do |json|
    changed_events = JSON.parse(json)
    event_ids = changed_events.map { |e| e['eventID'] }.join(', ')
    puts "Changed Events: #{event_ids}"

    # Fetch and update full event data
    api_get("/events", eventIDs: event_ids)['data'].each { |e| events[e['eventID']] = e }
  rescue => e
    puts "Error processing data event: #{e.message}"
  end

  # Print initial event IDs
  puts "EVENTS: #{events.keys.join(',')}"

  # Graceful shutdown
  trap('INT') { puts "\nDisconnecting..."; client.disconnect; exit }

  # Connect to the WebSocket server
  client.connect # For async usage, you can instead call client.connect(true) after initializing the client

end

private

# Runs the script
run

API Response Format

The /v2/stream/events endpoint returns:

json
{
  "success": true,
  "data": [
    <an Event object>,
    // ... more events
  ],
  "pusherKey": <key to pass when setting up the Pusher client>,
  "pusherOptions": <options to pass when setting up the Pusher client>,
  "channel": <channel to subscribe to>
}

Update Message Format

Real-time updates contain only the eventID of changed events:

json
[
  { "eventID": <an eventID> },
]

You then fetch full event details using the /v2/events endpoint with the eventIDs parameter.

Complete Example: NFL Game Tracker

This example demonstrates a production-ready implementation for tracking NFL games:

js
const axios = require("axios");
const Pusher = require("pusher-js");

const API_KEY = "PUT YOUR API KEY HERE";
const API_BASE_URL = "https://api.sportsgameodds.com/v2";
const LEAGUE_ID = "NFL";

// State variables
let events = new Map();
let pusher = null;
let channel = null;
let connectionState = "disconnected";
let heartbeatInterval = null;

const getEventTitle = (event) => {
  const awayTeam = event.teams?.away?.names?.medium || event.teams?.away?.names?.long || event.teams?.away?.names?.short;
  const homeTeam = event.teams?.home?.names?.medium || event.teams?.home?.names?.long || event.teams?.home?.names?.short;
  const gameTime = event.status.startsAt.toLocaleString();
  return `${awayTeam} vs ${homeTeam} @ ${gameTime} (${event.eventID})`;
};

const startMonitoring = () => {
  // Heartbeat
  heartbeatInterval = setInterval(() => {
    console.log(`💓 Heartbeat...connection: ${connectionState}, events tracked: ${events.size}`);
  }, 10000);

  // Connection state changes
  pusher.connection.bind("state_change", (states) => {
    connectionState = states.current;
    console.log(`🔌 Connection state: ${states.previous} → ${states.current}`);
  });

  // Connection errors
  pusher.connection.bind("error", (error) => {
    console.error("🚨 Connection error:", error);
  });

  // Connection established
  pusher.connection.bind("connected", () => {
    console.log("🎉 Connection established successfully");
  });

  // Connection disconnected
  pusher.connection.bind("disconnected", () => {
    console.log("👋 Connection disconnected");
  });

  // Failed connection
  pusher.connection.bind("failed", () => {
    console.log("💥 Connection failed permanently");
  });
};

const connect = async () => {
  console.log(`🔄 Connecting upcoming events for: ${LEAGUE_ID}...`);
  connectionState = "connecting";

  try {
    // Get stream metadata
    console.log("📡 Fetching stream configuration...");
    const response = await axios.get(`${API_BASE_URL}/stream/events`, {
      headers: { "x-api-key": API_KEY },
      params: {
        feed: "events:upcoming",
        leagueID: LEAGUE_ID,
      },
    });

    console.log("✅ Stream configuration received");
    const { data: initialEvents, pusherKey, pusherOptions, channel: channelName } = response.data;

    console.log(`📊 Stream config...channel: ${channelName}, pusherKey: ${pusherKey}, initialEvents: ${initialEvents.length}`);

    initialEvents.forEach((event) => {
      events.set(event.eventID, event);
      console.log(`⏳ Initial Event: ${getEventTitle(event)}`);
    });

    console.log("🔌 Initializing WebSocket connection...");
    pusher = new Pusher(pusherKey, pusherOptions);
    startMonitoring();

    // Subscribe to channel with error handling
    console.log(`📺 Subscribing to channel: ${channelName}`);
    channel = pusher.subscribe(channelName);

    channel.bind("pusher:subscription_succeeded", () => {
      console.log(`✅ Successfully subscribed to channel: ${channelName}`);
      connectionState = "subscribed";
    });

    channel.bind("data", async (changedEvents) => {
      console.log(`🔔 Received change notification for ${changedEvents.length} event(s)`);

      const eventIDs = changedEvents.map((e) => e.eventID).join(",");
      if (!eventIDs) return;

      console.log(`🔍 Fetching full data for events: ${eventIDs}`);
      const response = await axios
        .get(`${API_BASE_URL}/events`, {
          headers: { "x-api-key": API_KEY },
          params: { eventIDs },
        })
        .catch(({ data }) => data);
      if (!response?.data?.length) return;

      console.log(`📦 Received ${response.data.length} updated events`);

      response.data.forEach((current) => {
        const prev = events.get(current.eventID);
        if (!prev) {
          console.log(`🆕 New Event: ${getEventTitle(current)}`);
          return;
        } else if (!prev.status.started && current.status.started) {
          console.log(`🏈 Event started: ${getEventTitle(current)}`);
          return;
        } else {
          console.log(`🔄 Event updated: ${getEventTitle(current)}`);
        }
        events.set(current.eventID, current);
      });
    });

    console.log("🎯 Setup complete, waiting for events...");
  } catch (error) {
    console.log(error);
    console.error("❌ Failed to connect:", error.message);
    connectionState = "failed";
  }
};

const disconnect = () => {
  console.log("🔌 Disconnecting from stream...");

  if (heartbeatInterval) clearInterval(heartbeatInterval);
  if (channel?.name) pusher.unsubscribe(channel.name);
  if (pusher) pusher.disconnect();
};

console.log(`🚀 Starting upcoming event tracker for: ${LEAGUE_ID}...`);
connect();

process.on("SIGINT", () => {
  console.log("\n🛑 Shutdown signal received...");
  disconnect();
});

Feed-Specific Examples

Live Events

Monitor all currently live games across all sports:

js
axios.get("https://api.sportsgameodds.com/v2/stream/events", {
  headers: { 'x-api-key': API_KEY },
  params: {
    feed: 'events:live'
  }
});

Upcoming Events by League

Monitor upcoming games for a specific league:

js
axios.get("https://api.sportsgameodds.com/v2/stream/events", {
  headers: { 'x-api-key': API_KEY },
  params: {
    feed: 'events:upcoming',
    leagueID: 'NFL'
  }
});

Single Event Tracking

Track updates for one specific event:

js
axios.get("https://api.sportsgameodds.com/v2/stream/events", {
  headers: { 'x-api-key': API_KEY },
  params: {
    feed: 'events:byid',
    eventID: 'DENVER_NUGGETS_VS_MIAMI_HEAT_2024-05-20T00:30:00Z_NBA'
  }
});

Connection Management

You’ll want to be mindful of the connection state and handle errors and disconnections properly in order to maintain a reliable connection.

js
const pusher = new Pusher(pusherKey, pusherOptions);

// Monitor connection state
pusher.connection.bind('state_change', (states) => {
  console.log('Connection state:', states.current);
  // States: connecting, connected, unavailable, failed, disconnected
});

// Handle connection errors
pusher.connection.bind('error', (error) => {
  console.error('Connection error:', error);
  // Implement reconnection logic if needed
});

// Clean disconnect
pusher.disconnect();

Troubleshooting

Connection Issues

  • Verify your API key has streaming permissions
  • Check that you haven’t exceeded your concurrent stream limit
  • Ensure proper Pusher client library installation

No Updates Received

  • Confirm you’re subscribed to the correct channel
  • Verify the feed parameters match available events
  • Check network connectivity and firewall settings

High Memory Usage

  • Implement data cleanup for old events
  • Store only necessary event properties
  • Use efficient data structures (Map vs Object)

For additional support, contact our support team with your API key and specific error messages.