---
title: "Widget Tracking & Analytics Guide"
canonical_url: "https://apidocs.sportradar.com/resources/widgets/docs/tutorials/tracking-guide"
markdown_url: "https://apidocs.sportradar.com/resources/widgets/docs/tutorials/tracking-guide.md"
last_updated: "2026-03-23T13:02:40Z"
---

# 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

**Event Flow**

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

**Data Structure**

Every tracking event follows a consistent structure:

```js
onTrack(eventType, eventData)
```

**eventType** (string): The type of event that occurred
**eventData** (object): Rich contextual data about the event

```js
// Example event data structure
{
    // Widget context
    scheme: 'https',
    solution: 'sir',
    component: 'match.scoreboard',
    version: '2.1.0',
    
    // Content context  
    sportId: 1,
    matchId: 12345,
    seasonId: 67890,
    
    // User context
    language: 'en',
    channel: 'web',
    
    // Event-specific data
    visible: true,
    error: null
}
```

**Event Lifecycle**

**Widget Loading Phase:**

1. `data_change` - Initial data load
2. `data_change` - Subsequent updates (if any)

**User Interaction Phase:**

- `odds_click` - User clicks odds/betting buttons
- `social_share` - User shares content

**Error Handling:**

- `license_error` - Licensing issues
- `data_change` (with error) - Data loading problems

**Continuous Monitoring:**

- Performance metrics
- Visibility tracking
- Error monitoring

## 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:**

| Parameter | Type   | Description                     |
| --------- | ------ | ------------------------------- |
| eventType | string | The type of event that occurred |
| eventData | object | Contextual data about the event |

### Event Types

**data_change**

**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
            });
        }
    }
};
```

**odds_click**

**Triggered when:** User clicks on odds/betting buttons

**Use cases:**

- Track betting interest and engagement
- Monitor popular odds selections
- A/B test odds presentation
- Measure conversion funnel

**Event Data Structure:**

```js
{
    visible: true,           // Whether odds are visible
    odds: {
        value: 2.50,         // Odds value
        market: 'match_winner', // Market type
        selection: 'home',   // Selection identifier
        matchId: 12345,      // Associated match
        outcomeId: 'outcome_123'
    },
    widget: {
        component: 'match.scoreboard',
        version: '2.1.0'
    }
}
```

**Example Implementation:**

```js
const onTrack = (eventType, data) => {
    if (eventType === 'odds_click') {
        // Track betting engagement
        analytics.track('odds_clicked', {
            odds_value: data.odds?.value,
            market_type: data.odds?.market,
            match_id: data.odds?.matchId,
            is_visible: data.visible
        });
        
        // Business logic
        if (data.odds?.value > 3.0) {
            console.log('High-value odds clicked');
        }
    }
};
```

**social_share**

**Triggered when:** User clicks social sharing buttons

**Use cases:**

- Track content sharing and virality
- Monitor social engagement
- Optimize social features
- Measure reach and amplification

**Event Data Structure:**

```js
{
    source: 'match.scoreboard',    // Widget that initiated share
    target: 'facebook',            // Social platform ('facebook', 'twitter', 'linkedin', etc.)
    content: {
        matchId: 12345,
        title: 'Match Title',
        url: 'https://example.com/match/12345'
    },
    timestamp: '2024-10-09T10:30:00Z'
}
```

**Example Implementation:**

```js
const onTrack = (eventType, data) => {
    if (eventType === 'social_share') {
        // Track social sharing
        analytics.track('content_shared', {
            platform: data.target,
            widget_source: data.source,
            match_id: data.content?.matchId,
            share_url: data.content?.url
        });
        
        // Update share counters
        updateShareCount(data.target, data.content?.matchId);
    }
};
```

**license_error**

**Triggered when:** Widget encounters licensing issues

**Use cases:**

- Monitor license validity
- Debug integration issues
- Track service availability
- Implement fallback strategies

**Event Data Structure:**

```js
{
    error: 'License expired',     // Error message
    errorCode: 'LICENSE_EXPIRED', // Error code
    component: 'match.scoreboard',
    licenseInfo: {
        clientId: 'your_client_id',
        expiresAt: '2024-12-31T23:59:59Z',
        features: ['basic', 'odds']
    }
}
```

**Example Implementation:**

```js
const onTrack = (eventType, data) => {
    if (eventType === 'license_error') {
        // Log licensing error
        console.error('License error:', data.error);
        
        // Track in analytics
        analytics.track('license_error', {
            error_code: data.errorCode,
            component: data.component,
            client_id: data.licenseInfo?.clientId
        });
        
        // Show fallback content
        showFallbackContent();
        
        // Notify support team
        notifySupport('License error detected', data);
    }
};
```

## 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>
```

1. **Step 1**

   #### Create Analytics Dashboard

   Set up a visual dashboard to monitor events in real-time. This helps during development and debugging.

   The dashboard displays all tracked events with timestamps and data, providing immediate feedback on your tracking implementation.

2. **Step 2**

   #### Build Analytics System

   Create a flexible analytics system that can track events and integrate with your preferred analytics platform.

   The system stores events locally and provides methods to display and send data to external services like Google Analytics, Adobe Analytics, or custom solutions.

3. **Step 3**

   #### Implement Event Handlers

   Define comprehensive handlers for each event type. Each handler processes the event data and sends relevant information to your analytics system.

   This approach ensures consistent tracking across all widget interactions while allowing customization for specific business needs.

4. **Step 4**

   #### Track Performance Metrics

   Capture timing information to monitor widget loading performance. This helps identify performance bottlenecks and optimize user experience.

   Start timing when the tracking system initializes and calculate load time in the `data_change` event handler.

5. **Step 5**

   #### Initialize Widget System

   Load the Sportradar widget loader script and configure basic settings like language and theme preferences.

   This standardized initialization ensures consistent widget behavior across your site.

6. **Step 6**

   #### Load Widget With Tracking

   Finally, load your specific widget with the tracking callback attached. The widget will now send all trackable events to your analytics system.

   Monitor the dashboard to see events flowing in real-time as users interact with the widget.

## Analytics Platform Integration

Real-world examples for popular analytics platforms:

**Google Analytics 4**

```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;
    }
};
```

**Adobe Analytics**

```js
// Adobe Analytics integration
const trackingCallback = (eventType, data) => {
    switch(eventType) {
        case "data_change":
            if (!data.error) {
                s.linkTrackVars = 'events,eVar1,eVar2,eVar3,prop1,prop2';
                s.events = 'event1'; // Widget loaded
                s.eVar1 = data.component; // Widget type
                s.eVar2 = data.matchId; // Match ID
                s.eVar3 = data.sportId; // Sport ID
                s.prop1 = data.version; // Widget version
                s.prop2 = 'success'; // Load status
                s.tl(true, 'o', 'Widget Loaded');
            } else {
                s.linkTrackVars = 'events,eVar1,eVar4,prop2';
                s.events = 'event2'; // Widget error
                s.eVar1 = data.component;
                s.eVar4 = data.error.message;
                s.prop2 = 'error';
                s.tl(true, 'o', 'Widget Error');
            }
            break;
            
        case "odds_click":
            s.linkTrackVars = 'events,eVar5,eVar6,prop3';
            s.events = 'event3'; // Odds clicked
            s.eVar5 = data.odds?.market;
            s.eVar6 = data.odds?.value;
            s.prop3 = data.odds?.selection;
            s.tl(true, 'o', 'Odds Clicked');
            break;
            
        case "social_share":
            s.linkTrackVars = 'events,eVar7,prop4';
            s.events = 'event4'; // Social share
            s.eVar7 = data.target;
            s.prop4 = data.source;
            s.tl(true, 'o', 'Social Share');
            break;
    }
};
```

**Custom Analytics API**

```js
// Custom analytics API integration
const trackingCallback = (eventType, data) => {
    const baseEvent = {
        timestamp: new Date().toISOString(),
        user_id: getUserId(),
        session_id: getSessionId(),
        page_url: window.location.href,
        widget_event_type: eventType
    };

    let eventPayload;

    switch(eventType) {
        case "data_change":
            eventPayload = {
                ...baseEvent,
                event_name: data.error ? 'widget_error' : 'widget_loaded',
                properties: {
                    component: data.component,
                    version: data.version,
                    match_id: data.matchId,
                    sport_id: data.sportId,
                    language: data.language,
                    error: data.error
                }
            };
            break;
            
        case "odds_click":
            eventPayload = {
                ...baseEvent,
                event_name: 'odds_interaction',
                properties: {
                    odds_value: data.odds?.value,
                    market_type: data.odds?.market,
                    selection: data.odds?.selection,
                    match_id: data.odds?.matchId,
                    is_visible: data.visible
                }
            };
            break;
            
        case "social_share":
            eventPayload = {
                ...baseEvent,
                event_name: 'social_share',
                properties: {
                    platform: data.target,
                    source_widget: data.source,
                    content_url: data.content?.url
                }
            };
            break;
            
        case "license_error":
            eventPayload = {
                ...baseEvent,
                event_name: 'license_error',
                properties: {
                    error_code: data.errorCode,
                    error_message: data.error,
                    component: data.component,
                    client_id: data.licenseInfo?.clientId
                }
            };
            break;
    }

    // Send to custom analytics endpoint
    fetch('/api/analytics/track', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json',
        },
        body: JSON.stringify(eventPayload)
    }).catch(error => {
        console.error('Analytics tracking failed:', error);
    });
};

// Helper functions
function getUserId() {
    return localStorage.getItem('user_id') || 'anonymous';
}

function getSessionId() {
    return sessionStorage.getItem('session_id') || generateSessionId();
}

function generateSessionId() {
    const sessionId = 'session_' + Date.now() + '_' + Math.random().toString(36).substr(2, 9);
    sessionStorage.setItem('session_id', sessionId);
    return sessionId;
}
```

## Advanced Tracking Patterns

### Event Filtering & Sampling

For high-traffic applications, implement intelligent event filtering:

**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);
};
```

**Error Handling**

```js
// Robust error handling for tracking
class TrackingErrorHandler {
    constructor() {
        this.errors = [];
        this.maxErrors = 100;
        this.errorCallback = null;
    }
    
    setErrorCallback(callback) {
        this.errorCallback = callback;
    }
    
    handleError(error, context) {
        const errorEntry = {
            message: error.message,
            stack: error.stack,
            context: context,
            timestamp: new Date().toISOString(),
            userAgent: navigator.userAgent,
            url: window.location.href
        };
        
        this.errors.push(errorEntry);
        
        // Keep only recent errors
        if (this.errors.length > this.maxErrors) {
            this.errors = this.errors.slice(-this.maxErrors);
        }
        
        // Log error
        console.error('Tracking error:', errorEntry);
        
        // Call error callback if set
        if (this.errorCallback) {
            try {
                this.errorCallback(errorEntry);
            } catch (callbackError) {
                console.error('Error in tracking error callback:', callbackError);
            }
        }
    }
}

// Safe tracking wrapper
function createSafeTrackingCallback(originalCallback) {
    const errorHandler = new TrackingErrorHandler();
    
    return function safeTrackingCallback(eventType, data) {
        try {
            // Validate inputs
            if (typeof eventType !== 'string') {
                throw new Error('eventType must be a string');
            }
            
            if (typeof data !== 'object' || data === null) {
                throw new Error('data must be an object');
            }
            
            // Call original callback
            originalCallback(eventType, data);
            
        } catch (error) {
            errorHandler.handleError(error, {
                eventType: eventType,
                dataKeys: Object.keys(data || {}),
                timestamp: Date.now()
            });
        }
    };
}
```

## Privacy & GDPR Compliance

### Privacy-First Tracking

Implement tracking that respects user privacy and complies with regulations:

**Consent Management**

```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);
};
```

**Data Minimization**

```js
// Data minimization for privacy compliance
class DataMinimizer {
    constructor() {
        this.allowedFields = new Map([
            ['data_change', ['component', 'version', 'sportId', 'matchId', 'error']],
            ['odds_click', ['odds.value', 'odds.market', 'visible']],
            ['social_share', ['target', 'source']],
            ['license_error', ['error', 'errorCode', 'component']]
        ]);
        
        this.sensitivePatterns = [
            /email/i,
            /phone/i,
            /address/i,
            /ssn/i,
            /credit/i,
            /password/i,
            /token/i
        ];
    }
    
    minimize(eventType, data) {
        const allowedFields = this.allowedFields.get(eventType) || [];
        const minimized = {};
        
        // Only include allowed fields
        allowedFields.forEach(field => {
            const value = this.getNestedValue(data, field);
            if (value !== undefined) {
                this.setNestedValue(minimized, field, value);
            }
        });
        
        // Remove sensitive data
        return this.removeSensitiveData(minimized);
    }
    
    getNestedValue(obj, path) {
        return path.split('.').reduce((current, key) => {
            return current && current[key] !== undefined ? current[key] : undefined;
        }, obj);
    }
    
    setNestedValue(obj, path, value) {
        const keys = path.split('.');
        const lastKey = keys.pop();
        const target = keys.reduce((current, key) => {
            current[key] = current[key] || {};
            return current[key];
        }, obj);
        target[lastKey] = value;
    }
    
    removeSensitiveData(data) {
        const cleaned = {};
        
        for (const [key, value] of Object.entries(data)) {
            if (this.isSensitiveField(key)) {
                continue; // Skip sensitive fields
            }
            
            if (typeof value === 'object' && value !== null) {
                cleaned[key] = this.removeSensitiveData(value);
            } else {
                cleaned[key] = value;
            }
        }
        
        return cleaned;
    }
    
    isSensitiveField(fieldName) {
        return this.sensitivePatterns.some(pattern => pattern.test(fieldName));
    }
}

// Usage
const dataMinimizer = new DataMinimizer();

const privacyCompliantCallback = (eventType, data) => {
    const minimizedData = dataMinimizer.minimize(eventType, data);
    
    // Add standard privacy-safe metadata
    const trackingData = {
        ...minimizedData,
        timestamp: new Date().toISOString(),
        page_url: window.location.pathname, // No query params
        user_agent: navigator.userAgent.split(' ')[0], // Simplified UA
        session_id: getSessionId() // Anonymized session
    };
    
    // Send to analytics
    sendToAnalytics(eventType, trackingData);
};
```

**Cookie-Free Tracking**

```js
// Cookie-free tracking implementation
class CookieFreeTracker {
    constructor() {
        this.sessionId = this.generateSessionId();
        this.pageViewId = this.generatePageViewId();
        this.startTime = Date.now();
    }
    
    generateSessionId() {
        // Generate session ID without cookies
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        ctx.textBaseline = 'top';
        ctx.font = '14px Arial';
        ctx.fillText('Session fingerprint', 2, 2);
        
        const fingerprint = canvas.toDataURL()
            .replace(/^data:image\/\w+;base64,/, '')
            .substring(0, 16);
        
        return 'session_' + fingerprint + '_' + Date.now().toString(36);
    }
    
    generatePageViewId() {
        return 'page_' + Math.random().toString(36).substr(2, 9) + '_' + Date.now();
    }
    
    track(eventType, data) {
        const event = {
            event_type: eventType,
            event_data: data,
            session_id: this.sessionId,
            page_view_id: this.pageViewId,
            timestamp: new Date().toISOString(),
            time_on_page: Date.now() - this.startTime,
            
            // Basic context without PII
            viewport: {
                width: window.innerWidth,
                height: window.innerHeight
            },
            referrer: document.referrer ? new URL(document.referrer).hostname : null,
            language: navigator.language,
            timezone: Intl.DateTimeFormat().resolvedOptions().timeZone
        };
        
        this.sendEvent(event);
    }
    
    sendEvent(event) {
        // Send using sendBeacon for reliability
        if (navigator.sendBeacon) {
            navigator.sendBeacon('/api/analytics/track', JSON.stringify(event));
        } else {
            // Fallback to fetch
            fetch('/api/analytics/track', {
                method: 'POST',
                headers: { 'Content-Type': 'application/json' },
                body: JSON.stringify(event),
                keepalive: true
            }).catch(() => {
                // Ignore errors silently
            });
        }
    }
}

// Cookie-free tracking callback
const cookieFreeTracker = new CookieFreeTracker();
const trackingCallback = (eventType, data) => {
    cookieFreeTracker.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

**Event Inspector**

```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);
};
```

**Testing Utilities**

```js
// Testing utilities for tracking
class TrackingTester {
    constructor() {
        this.expectations = [];
        this.actualEvents = [];
    }
    
    expect(eventType, dataMatchers = {}) {
        this.expectations.push({ eventType, dataMatchers });
        return this;
    }
    
    record(eventType, data) {
        this.actualEvents.push({ eventType, data, timestamp: Date.now() });
    }
    
    verify() {
        const results = {
            passed: 0,
            failed: 0,
            details: []
        };
        
        this.expectations.forEach((expectation, index) => {
            const matchingEvent = this.actualEvents.find(event => {
                if (event.eventType !== expectation.eventType) return false;
                
                // Check data matchers
                for (const [key, expectedValue] of Object.entries(expectation.dataMatchers)) {
                    if (event.data[key] !== expectedValue) return false;
                }
                
                return true;
            });
            
            if (matchingEvent) {
                results.passed++;
                results.details.push({
                    index,
                    status: 'PASS',
                    expectation,
                    actual: matchingEvent
                });
            } else {
                results.failed++;
                results.details.push({
                    index,
                    status: 'FAIL',
                    expectation,
                    actual: null
                });
            }
        });
        
        return results;
    }
    
    reset() {
        this.expectations = [];
        this.actualEvents = [];
    }
}

// Usage in tests
const tester = new TrackingTester();

// Set up expectations
tester
    .expect('data_change', { component: 'match.scoreboard' })
    .expect('odds_click', { visible: true });

// Test tracking callback
const testTrackingCallback = (eventType, data) => {
    tester.record(eventType, data);
    // Your actual tracking logic here
};

// Load widget and trigger events...

// Verify results
setTimeout(() => {
    const results = tester.verify();
    console.log('Tracking test results:', results);
}, 2000);
```

## 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.](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/examples/example-widget-ontrack-error.md)
