Skip to main content
Logo
Explore APIsContact Us
  • Home
  • Match Preview
  • Tournament Preview
  • Virtual Stadium
  1. Resources
  2. Widgets
  3. Widget Tracking & Analytics Guide

Widget Tracking & Analytics Guide

#What Is Widget Tracking?

Widget tracking allows you to monitor user interactions, performance metrics, and business insights from Sportradar widgets integrated into your website. With comprehensive tracking, you can:

  • Monitor User Engagement - Track clicks, interactions, and user behavior patterns
  • Measure Performance - Monitor loading times, error rates, and success metrics
  • Optimize User Experience - Identify pain points and improve widget placement
  • Drive Business Insights - Analyze user preferences and content effectiveness
  • Ensure Reliability - Detect and respond to errors automatically

The tracking system provides real-time event data through the onTrack callback function, enabling seamless integration with your analytics infrastructure.

#Intended Audience

This tutorial is intended for engineers involved in integration of Sportradar widgets into their online infrastructure, who are building custom tracking of how Sportradar widgets are used inside their online environment.

#Goals

In this tutorial you will learn:

  • Tracking Architecture - How the widget tracking system works
  • Event Types - Complete documentation of all trackable events
  • Implementation Patterns - Best practices for different tracking scenarios
  • Analytics Integration - Examples for popular analytics platforms
  • Performance Optimization - Efficient tracking without impacting user experience
  • Privacy Compliance - GDPR and privacy-friendly tracking approaches

#Prerequisites

To get through this tutorial, you will need:

  • a website with integrated Sportradar widgets
  • a supported web browser (Chrome 60+, Firefox 55+, Safari 12+, Edge 79+)
  • a development environment (VS Code, VIM, EMACS, Notepad++, IntelliJ, Eclipse, PyCharm)
  • basic knowledge of JavaScript and event handling
  • access to an analytics platform (optional, for integration examples)

#Tracking Architecture

Understanding how widget tracking works helps you implement effective monitoring:

#How Tracking Works

  1. Widget Initialization - Widget starts loading and prepares event system
  2. Event Triggers - User interactions or system events occur within widget
  3. Event Processing - Widget captures event data and metadata
  4. Callback Execution - Your onTrack function receives the event
  5. Analytics Integration - Your code processes and forwards data to analytics

Key Benefits:

  • Real-time event streaming
  • Rich contextual data
  • Reliable event delivery
  • Minimal performance impact

#Event Types & API Specification

#onTrack Function

The onTrack function is a callback property available on every widget's API. This function is invoked whenever any trackable event occurs within the widget.

Function Signature:

typescript
function onTrack(eventType: string, eventData: object): void

Parameters:

ParameterTypeDescription
eventTypestringThe type of event that occurred
eventDataobjectContextual data about the event

#Event Types

Triggered when: Widget receives new data or data updates

Use cases:

  • Monitor widget loading success/failure
  • Track data refresh cycles
  • Implement error handling
  • Show/hide widgets based on data availability

Event Data Structure:

js
{
    // Widget metadata
    scheme: 'https',
    solution: 'sir', 
    component: 'match.scoreboard',
    version: '2.1.0',
    language: 'en',
    channel: 'web',
    
    // Content identifiers
    sportId: 1,
    categoryId: 123,
    tournamentId: 456,
    seasonId: 789,
    matchId: 12345,
    
    // Team information (when applicable)
    team1uid: 'team_123',
    team2uid: 'team_456',
    
    // Location data (when applicable)
    continentId: 1,
    venueId: 789,
    
    // User context
    user: 'anonymous_user_id',
    
    // Error information (when error occurs)
    error: null | {
        message: 'Error description',
        code: 'ERROR_CODE',
        details: {}
    }
}

Example Implementation:

js
const onTrack = (eventType, data) => {
    if (eventType === 'data_change') {
        if (data.error) {
            console.error('Widget failed to load:', data.error);
            // Hide widget or show error message
            document.getElementById('widget-container').style.display = 'none';
        } else {
            console.log('Widget loaded successfully:', data.component);
            // Analytics tracking
            analytics.track('widget_loaded', {
                widget_type: data.component,
                match_id: data.matchId,
                sport_id: data.sportId
            });
        }
    }
};

#Interactive Implementation Tutorial

Let's build a comprehensive tracking system step by step:

html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Widget Tracking Implementation</title>
    <style>
        .analytics-dashboard {
            background: #f5f5f5;
            padding: 20px;
            margin: 10px 0;
            border-radius: 8px;
        }
        .event-log {
            background: #fff;
            border: 1px solid #ddd;
            padding: 10px;
            max-height: 200px;
            overflow-y: auto;
            font-family: monospace;
        }
    </style>
</head>
<body>
    <div class="analytics-dashboard">
        <h3>Live Analytics Dashboard</h3>
        <div id="event-log" class="event-log"></div>
    </div>
    
    <div id="sr-widget"></div>
    
    <script>
        // Analytics tracking system
        const analytics = {
            events: [],
            track: function(eventName, properties) {
                const event = {
                    name: eventName,
                    properties: properties,
                    timestamp: new Date().toISOString()
                };
                this.events.push(event);
                this.displayEvent(event);
                // Send to your analytics service
                this.sendToAnalytics(event);
            },
            displayEvent: function(event) {
                const log = document.getElementById('event-log');
                const entry = document.createElement('div');
                entry.textContent = `${event.timestamp}: ${event.name} - ${JSON.stringify(event.properties)}`;
                log.appendChild(entry);
                log.scrollTop = log.scrollHeight;
            },
            sendToAnalytics: function(event) {
                // Integration with your analytics platform
                console.log('Sending to analytics:', event);
            }
        };

        // Comprehensive tracking callback
        const trackingCallback = (eventType, data) => {
            switch(eventType) {
                case "data_change":
                    if (data.error) {
                        analytics.track('widget_error', {
                            component: data.component,
                            error_message: data.error.message,
                            match_id: data.matchId
                        });
                    } else {
                        analytics.track('widget_loaded', {
                            component: data.component,
                            match_id: data.matchId,
                            sport_id: data.sportId,
                            load_time: Date.now() - startTime
                        });
                    }
                    break;
                    
                case "odds_click":
                    analytics.track('odds_engagement', {
                        odds_value: data.odds?.value,
                        market_type: data.odds?.market,
                        is_visible: data.visible
                    });
                    break;
                    
                case "social_share":
                    analytics.track('content_shared', {
                        platform: data.target,
                        widget_source: data.source
                    });
                    break;
                    
                case "license_error":
                    analytics.track('license_error', {
                        error_code: data.errorCode,
                        component: data.component
                    });
                    break;
                    
                default:
                    analytics.track('unknown_event', {
                        event_type: eventType,
                        data: data
                    });
            }
        };

        // Track widget load start time
        const startTime = Date.now();
        
        // Initialize Sportradar widgets
        (function(a,b,c,d,e,f,g,h,i){a[e]||(i=a[e]=function(){(a[e].q=a[e].q||[]).push(arguments)},i.l=1*new Date,i.o=f,
        g=b.createElement(c),h=b.getElementsByTagName(c)[0],g.async=1,g.src=d,g.setAttribute("n",e),h.parentNode.insertBefore(g,h)
        )})(window,document,"script","https://widgets.sir.sportradar.com/sportradar/widgetloader","SIR", {
            language: 'en'
        });
        
        // Load widget with tracking
        SIR('addWidget', '#sr-widget', 'match.scoreboard', {
            matchId: 12345,
            onTrack: trackingCallback
        });
    </script>
</body>
</html>

#Analytics Platform Integration

Real-world examples for popular analytics platforms:

js
// Google Analytics 4 integration
const trackingCallback = (eventType, data) => {
    switch(eventType) {
        case "data_change":
            if (!data.error) {
                gtag('event', 'widget_loaded', {
                    event_category: 'Widget',
                    event_label: data.component,
                    custom_parameters: {
                        match_id: data.matchId,
                        sport_id: data.sportId,
                        widget_version: data.version
                    }
                });
            } else {
                gtag('event', 'widget_error', {
                    event_category: 'Widget',
                    event_label: data.component,
                    custom_parameters: {
                        error_message: data.error.message,
                        error_code: data.error.code
                    }
                });
            }
            break;
            
        case "odds_click":
            gtag('event', 'odds_interaction', {
                event_category: 'Engagement',
                event_label: 'odds_click',
                value: data.odds?.value || 0,
                custom_parameters: {
                    market_type: data.odds?.market,
                    selection: data.odds?.selection,
                    match_id: data.odds?.matchId
                }
            });
            break;
            
        case "social_share":
            gtag('event', 'share', {
                method: data.target,
                content_type: 'widget',
                content_id: data.source
            });
            break;
    }
};

#Advanced Tracking Patterns

#Event Filtering & Sampling

For high-traffic applications, implement intelligent event filtering:

js
// Advanced event filtering system
class EventFilter {
    constructor() {
        this.filters = new Map();
        this.rateLimits = new Map();
    }
    
    // Add filter conditions
    addFilter(eventType, filterFn) {
        if (!this.filters.has(eventType)) {
            this.filters.set(eventType, []);
        }
        this.filters.get(eventType).push(filterFn);
    }
    
    // Add rate limiting
    addRateLimit(eventType, maxEventsPerMinute) {
        this.rateLimits.set(eventType, {
            max: maxEventsPerMinute,
            events: [],
            lastCleanup: Date.now()
        });
    }
    
    // Check if event should be tracked
    shouldTrack(eventType, data) {
        // Apply filters
        const filters = this.filters.get(eventType) || [];
        for (const filter of filters) {
            if (!filter(data)) {
                return false;
            }
        }
        
        // Apply rate limiting
        if (this.rateLimits.has(eventType)) {
            return this.checkRateLimit(eventType);
        }
        
        return true;
    }
    
    checkRateLimit(eventType) {
        const limit = this.rateLimits.get(eventType);
        const now = Date.now();
        
        // Clean old events (older than 1 minute)
        if (now - limit.lastCleanup > 60000) {
            limit.events = limit.events.filter(time => now - time < 60000);
            limit.lastCleanup = now;
        }
        
        if (limit.events.length >= limit.max) {
            return false;
        }
        
        limit.events.push(now);
        return true;
    }
}

// Usage example
const eventFilter = new EventFilter();

// Only track odds clicks for high-value odds
eventFilter.addFilter('odds_click', (data) => {
    return data.odds?.value > 2.0;
});

// Limit data_change events to 10 per minute
eventFilter.addRateLimit('data_change', 10);

// Enhanced tracking callback with filtering  
const trackingCallback = (eventType, data) => {
    if (!eventFilter.shouldTrack(eventType, data)) {
        return; // Skip tracking this event
    }
    
    // Proceed with normal tracking
    processEvent(eventType, data);
};

#Privacy & GDPR Compliance

#Privacy-First Tracking

Implement tracking that respects user privacy and complies with regulations:

js
// Privacy-compliant tracking system
class PrivacyTracker {
    constructor() {
        this.hasConsent = false;
        this.consentTypes = new Set();
        this.queuedEvents = [];
        this.anonymousMode = true;
    }
    
    // Set user consent status
    setConsent(consentGiven, consentTypes = []) {
        this.hasConsent = consentGiven;
        this.consentTypes = new Set(consentTypes);
        this.anonymousMode = !consentGiven;
        
        if (consentGiven) {
            // Process queued events
            this.processQueuedEvents();
        } else {
            // Clear any stored data
            this.clearStoredData();
        }
    }
    
    // Track event with privacy checks
    track(eventType, data) {
        // Always allow essential tracking
        if (this.isEssentialEvent(eventType)) {
            return this.processEvent(eventType, this.sanitizeData(data));
        }
        
        // Check consent for non-essential tracking
        if (!this.hasConsent) {
            // Queue for later or discard
            this.queuedEvents.push({ eventType, data, timestamp: Date.now() });
            return;
        }
        
        // Check specific consent types
        if (!this.hasConsentForEvent(eventType)) {
            return; // Skip tracking
        }
        
        this.processEvent(eventType, data);
    }
    
    isEssentialEvent(eventType) {
        // Define which events are essential for functionality
        const essentialEvents = ['license_error', 'data_change'];
        return essentialEvents.includes(eventType);
    }
    
    hasConsentForEvent(eventType) {
        const eventConsentMap = {
            'odds_click': 'analytics',
            'social_share': 'marketing',
            'data_change': 'functional'
        };
        
        const requiredConsent = eventConsentMap[eventType];
        return !requiredConsent || this.consentTypes.has(requiredConsent);
    }
    
    sanitizeData(data) {
        if (this.anonymousMode) {
            // Remove or hash personally identifiable information
            const sanitized = { ...data };
            delete sanitized.user;
            delete sanitized.userId;
            delete sanitized.email;
            delete sanitized.ipAddress;
            return sanitized;
        }
        return data;
    }
    
    processQueuedEvents() {
        const events = [...this.queuedEvents];
        this.queuedEvents = [];
        
        events.forEach(event => {
            this.track(event.eventType, event.data);
        });
    }
    
    clearStoredData() {
        // Clear any tracking data stored locally
        localStorage.removeItem('tracking_data');
        sessionStorage.removeItem('session_events');
        this.queuedEvents = [];
    }
    
    processEvent(eventType, data) {
        // Your actual tracking implementation
        console.log('Tracking event:', eventType, data);
    }
}

// Usage with consent management
const privacyTracker = new PrivacyTracker();

// Check for existing consent (from cookie banner, etc.)
const savedConsent = localStorage.getItem('user_consent');
if (savedConsent) {
    const consent = JSON.parse(savedConsent);
    privacyTracker.setConsent(consent.given, consent.types);
}

// Update consent when user makes choice
function updateConsent(consentGiven, consentTypes) {
    privacyTracker.setConsent(consentGiven, consentTypes);
    localStorage.setItem('user_consent', JSON.stringify({
        given: consentGiven,
        types: consentTypes,
        timestamp: Date.now()
    }));
}

// Privacy-compliant tracking callback
const trackingCallback = (eventType, data) => {
    privacyTracker.track(eventType, data);
};

#Debugging & Troubleshooting

#Common Issues and Solutions

Events not firing

Possible causes:

  • Widget not loading properly
  • Incorrect onTrack function signature
  • JavaScript errors preventing callback execution
  • Event filtering blocking events

Solutions:

js
// Debug tracking implementation
function debugTrackingCallback(eventType, data) {
    console.log('🔍 Tracking Debug:', {
        eventType,
        data,
        timestamp: new Date().toISOString()
    });
    
    // Validate parameters
    if (typeof eventType !== 'string') {
        console.error('❌ eventType should be string, got:', typeof eventType);
        return;
    }
    
    if (typeof data !== 'object') {
        console.error('❌ data should be object, got:', typeof data);
        return;
    }
    
    // Call your actual tracking
    try {
        yourTrackingFunction(eventType, data);
        console.log('✅ Event tracked successfully');
    } catch (error) {
        console.error('❌ Tracking failed:', error);
    }
}

Missing event data

Possible causes:

  • Widget configuration missing required parameters
  • Data not yet loaded when event fires
  • Event data structure changed

Solutions:

js
// Validate and enrich event data
function validateEventData(eventType, data) {
    const requiredFields = {
        'data_change': ['component', 'version'],
        'odds_click': ['visible'],
        'social_share': ['source', 'target'],
        'license_error': ['error', 'component']
    };
    
    const required = requiredFields[eventType] || [];
    const missing = required.filter(field => !data.hasOwnProperty(field));
    
    if (missing.length > 0) {
        console.warn(`Missing required fields for ${eventType}:`, missing);
    }
    
    // Enrich with fallback data
    return {
        ...data,
        _timestamp: Date.now(),
        _url: window.location.href,
        _eventType: eventType
    };
}

Performance impact

Possible causes:

  • Synchronous analytics calls
  • Large event payloads
  • Too frequent events

Solutions:

js
// Performance monitoring
class PerformanceTracker {
    constructor() {
        this.metrics = [];
    }
    
    measureTracking(eventType, trackingFn) {
        const start = performance.now();
        
        try {
            trackingFn();
            const duration = performance.now() - start;
            
            this.metrics.push({
                eventType,
                duration,
                timestamp: Date.now()
            });
            
            if (duration > 50) { // Warn if tracking takes >50ms
                console.warn(`Slow tracking for ${eventType}: ${duration.toFixed(2)}ms`);
            }
            
        } catch (error) {
            console.error('Tracking performance measurement failed:', error);
        }
    }
    
    getAverageTime(eventType) {
        const events = this.metrics.filter(m => m.eventType === eventType);
        if (events.length === 0) return 0;
        
        const total = events.reduce((sum, event) => sum + event.duration, 0);
        return total / events.length;
    }
}

#Development Tools

js
// Development-only event inspector
class TrackingInspector {
    constructor() {
        this.events = [];
        this.isEnabled = window.location.search.includes('debug=tracking');
        
        if (this.isEnabled) {
            this.createInspectorUI();
        }
    }
    
    inspect(eventType, data) {
        if (!this.isEnabled) return;
        
        const event = {
            type: eventType,
            data: data,
            timestamp: new Date().toISOString(),
            id: Math.random().toString(36).substr(2, 9)
        };
        
        this.events.push(event);
        this.updateUI(event);
    }
    
    createInspectorUI() {
        const inspector = document.createElement('div');
        inspector.id = 'tracking-inspector';
        inspector.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            width: 300px;
            max-height: 400px;
            background: #f0f0f0;
            border: 1px solid #ccc;
            border-radius: 4px;
            font-family: monospace;
            font-size: 12px;
            z-index: 10000;
            overflow: hidden;
        `;
        
        inspector.innerHTML = `
            <div style="background: #333; color: white; padding: 8px;">
                🔍 Tracking Inspector
                <button onclick="this.parentNode.parentNode.remove()" style="float: right;">×</button>
            </div>
            <div id="inspector-events" style="max-height: 350px; overflow-y: auto; padding: 8px;"></div>
        `;
        
        document.body.appendChild(inspector);
    }
    
    updateUI(event) {
        const container = document.getElementById('inspector-events');
        if (!container) return;
        
        const eventEl = document.createElement('div');
        eventEl.style.cssText = `
            border-bottom: 1px solid #ddd;
            padding: 4px 0;
            margin-bottom: 4px;
        `;
        
        eventEl.innerHTML = `
            <strong style="color: #007acc;">${event.type}</strong>
            <span style="color: #666; font-size: 10px;">${event.timestamp}</span>
            <details>
                <summary>Data</summary>
                <pre style="font-size: 10px; max-height: 100px; overflow: auto;">${JSON.stringify(event.data, null, 2)}</pre>
            </details>
        `;
        
        container.insertBefore(eventEl, container.firstChild);
        
        // Keep only last 20 events
        while (container.children.length > 20) {
            container.removeChild(container.lastChild);
        }
    }
    
    exportEvents() {
        const dataStr = JSON.stringify(this.events, null, 2);
        const dataBlob = new Blob([dataStr], { type: 'application/json' });
        const url = URL.createObjectURL(dataBlob);
        
        const link = document.createElement('a');
        link.href = url;
        link.download = `tracking-events-${Date.now()}.json`;
        link.click();
        
        URL.revokeObjectURL(url);
    }
}

// Usage: Add ?debug=tracking to URL to enable
const inspector = new TrackingInspector();

const debugTrackingCallback = (eventType, data) => {
    inspector.inspect(eventType, data);
    
    // Your normal tracking
    yourTrackingFunction(eventType, data);
};

#Best Practices Summary

tip

Performance & Privacy:

  • Implement consent management before tracking
  • Use event batching for high-traffic sites
  • Minimize data collection to essential metrics only
  • Consider cookie-free tracking approaches
  • Monitor tracking performance impact

Reliability:

  • Always validate event data structure
  • Implement error handling and recovery
  • Use debugging tools during development
  • Test tracking across different scenarios
  • Monitor tracking success rates in production

Compliance:

  • Follow GDPR and privacy regulations
  • Implement data retention policies
  • Provide opt-out mechanisms
  • Document what data you collect and why
  • Regular privacy impact assessments

Advanced example demonstrating how to use onTrack callback to preload widget and display it only if there is no error, can be found here.

Last updated 14 days ago
Is this site helpful?
Widgets, Engagement Tools
Getting StartedFrequently Asked Questions
On this page
  • What Is Widget Tracking?
  • Intended Audience
  • Goals
  • Prerequisites
  • Tracking Architecture
  • How Tracking Works
  • Event Types & API Specification
  • onTrack Function
  • Event Types
  • Interactive Implementation Tutorial
  • Analytics Platform Integration
  • Advanced Tracking Patterns
  • Event Filtering & Sampling
  • Privacy & GDPR Compliance
  • Privacy-First Tracking
  • Debugging & Troubleshooting
  • Common Issues and Solutions
  • Development Tools
  • Best Practices Summary