Skip to main content Open Assistant Open Assistant Widget Tracking & Analytics Guide | Engagement Tools | API Hub
Engagement Tools Widget Tracking & Analytics Guide Widgets, Engagement Tools, BET
Last updated 3 months ago
Is this site helpful?
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
Data Structure
Event Lifecycle
# 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:
Expanded Table Parameter Type Description eventType string The type of event that occurred eventData object Contextual data about the event
# Event Types
data_change
odds_click
social_share
license_error
# Interactive Implementation Tutorial
Let's build a comprehensive tracking system step by step:
Real-world examples for popular analytics platforms:
Google Analytics 4
Adobe Analytics
Custom Analytics API
# Advanced Tracking Patterns
# Event Filtering & Sampling
For high-traffic applications, implement intelligent event filtering:
Event Filtering
Error Handling
# Privacy & GDPR Compliance
# Privacy-First Tracking
Implement tracking that respects user privacy and complies with regulations:
Consent Management
Data Minimization
Cookie-Free Tracking
# 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
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' ]
Performance impact
Possible causes:
Synchronous analytics calls
Large event payloads
Too frequent events
Solutions:
js
// Performance monitoring
class PerformanceTracker {
constructor () {
this . metrics = [] ;
}
measureTracking ( eventType
Event Inspector
Testing Utilities
# Best Practices Summary
Advanced example demonstrating how to use onTrack callback to preload widget and display it only if there is no error, can be found here.
Widget Initialization - Widget starts loading and prepares event system
Event Triggers - User interactions or system events occur within widget
Event Processing - Widget captures event data and metadata
Callback Execution - Your onTrack function receives the event
Analytics Integration - Your code processes and forwards data to analytics
Real-time event streaming
Rich contextual data
Reliable event delivery
Minimal performance impact
Triggered when: Widget receives new data or data updates
Monitor widget loading success/failure
Track data refresh cycles
Implement error handling
Show/hide widgets based on data availability
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 : {}
}
} 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
} ) ;
}
}
};
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.
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.
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.
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.
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.
html
<!DOCTYPE html >
< html >
< head >
< meta charset = "UTF-8" >
< title > Widget Tracking Implementation </ title >
< style >
. analytics-dashboard {
background : # f5f5f5 ;
padding : 20 px ;
margin : 10 px 0 ;
border-radius : 8 px ;
}
. event-log {
background : #
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 ;
}
}; 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) ;
}; 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 .
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) ;
}
}
,
'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
};
}
,
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 ;
}
}
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
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
fff
;
border : 1 px solid # ddd ;
padding : 10 px ;
max-height : 200 px ;
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 >
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) ;
};
.
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) ;
};