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:
The tracking system provides real-time event data through the onTrack callback function, enabling seamless integration with your analytics infrastructure.
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.
In this tutorial you will learn:
To get through this tutorial, you will need:
Understanding how widget tracking works helps you implement effective monitoring:
onTrack function receives the eventKey Benefits:
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:
function onTrack(eventType: string, eventData: object): voidParameters:
| Parameter | Type | Description |
|---|---|---|
| eventType | string | The type of event that occurred |
| eventData | object | Contextual data about the event |
Triggered when: Widget receives new data or data updates
Use cases:
Event Data Structure:
{
// 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:
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
});
}
}
};Let's build a comprehensive tracking system step by step:
<!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: #
Real-world examples for popular analytics platforms:
// 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;
}
};For high-traffic applications, implement intelligent event filtering:
// 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);
};Implement tracking that respects user privacy and complies with regulations:
// 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.
Events not firing
Possible causes:
onTrack function signatureSolutions:
// 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:
Solutions:
// 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:
Solutions:
// 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-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
Performance & Privacy:
Reliability:
Compliance: