Skip to main content
Logo
Explore APIsContact Us
  • Home
  1. Resources
  2. Virtual Stadium
  3. Getting Started

Getting Started

The Virtual Stadium adapter is the core component you implement to handle all communication between Virtual Stadium widgets and your betting platform. It processes data requests from widgets and integrates with your backend systems. Custom Adapter gives you full control over the integration, allowing you to implement and host all adapter functionality on your own infrastructure. This approach provides maximum flexibility for custom betting workflows while maintaining compatibility with Virtual Stadium's modular architecture.

Custom Adapter Implementation Timeline

#Prerequisites

Before beginning custom adapter implementation, complete these foundational steps:

Phase 1: Initiation (1-2 Days)

  • Communication Channel - Sportradar initiates shared Slack channel
  • RSA Key Generation - Generate 2048-bit RSA keys for JWT authentication → Guide
  • Language Requirements - Specify supported languages via Slack channel
  • Moderation Setup - Sportradar configures moderation client with Supervisor access

Phase 2: Technical Kickoff Meeting

  • Supervisor Access - Provide supervisor email for moderation access
  • Introduction Session - Learn about features, adapter architecture, and integration process
  • Adapter Decision - Confirm custom adapter is the right choice for your needs

Phase 3: Post-Kickoff Setup

  • Brand & Operator Setup - Supervisor configures organizational structure (~1 day) → Managing Brands
  • Moderator Invitation - Supervisor invites moderation team members → Managing Users
  • Channel Setup - Create channels manually or via API → Channel Creation
  • JWT Implementation - Implement token generation on your backend → JWT Authentication

View Complete Integration Timeline →

#Implementation Timeline

The custom adapter implementation follows a structured timeline where you handle all development and hosting:

#Sportradar's Responsibility

  • Technical Guidance

#Your Responsibility

Module Implementation:

  • Bet Share - Share bet selections between users
  • Flash Bet - Live event triggered betting
  • Bet Insights - AI-powered betting suggestions

#Phase 1: Adapter Registration

Register your adapter before adding Virtual Stadium widgets to the page. The adapter function handles all communication between widgets and your backend.

#Adapter Function

requestName string required

The type of data being requested (e.g., bet details, user balance).

args Record<string, any> required

Arguments specific to the request.

callback function required

Function to execute when data is available or updated.

info

Request names and arguments vary by widget. Refer to individual module documentation for specific requirements.

Adapter Registration:

javascript
function onRequest(requestName, args, callback) {
    switch (requestName) {
        case 'someFeature':
            // Store callback reference for potential subscriptions
            // Implement data fetching and processing logic
            // Execute callback with results or errors
            break;

        default:
        // Handle unknown requests - you can:
        // - Ignore silently for unimplemented features
        // - Send to analytics/monitoring system
        // - Log for debugging during development
    }
}

SIR('registerAdapter', onRequest);

#Phase 2: Module Development

Each module requires specific API endpoints and data formats. See individual module documentation for detailed requirements.

Bet Share - Social betting features

Bet Share Implementation

Flash Bet - Live betting features

Flash Bet Implementation

Bet Insights - AI-powered suggestions

Bet Insights Implementation


#Next Steps

  • Not yet integrated the widget? → Start with Virtual Stadium Quick Start for complete widget setup

Complete Integration Example with Mock Data:

html
<!-- Load the SIR widget loader script -->
<script>
    /**
     * TODO(developer): Replace <CLIENT_ID> with your actual client ID
     * provided by Sportradar before running this code.
     */
    (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/<CLIENT_ID>/widgetloader', 'SIR', {
        language: 'en'
    });

    // Mock data for demonstration - replace with real API calls
    const mockData = {
        betShare: [
            {
                id: 'bet_123',
                title: 'Great bet!',
                stake: 50,
                odds: 2.5,
                selections: [
                    {
                        eventId: 'event_456',
                        marketId: 'market_789',
                        outcomeId: 'outcome_101',
                        eventName: 'Team A vs Team B',
                        marketName: 'Match Winner',
                        outcomeName: 'Team A'
                    }
                ]
            }
        ],
        matchEventMarkets: {
            goal: {
                markets: [
                    {
                        id: 'goal_market',
                        name: 'Goal Scorer',
                        outcomes: [
                            { id: 'player_1', name: 'Player A', odds: 3.0 },
                            { id: 'player_2', name: 'Player B', odds: 3.5 }
                        ]
                    }
                ]
            },
            card: {
                markets: [
                    {
                        id: 'card_market',
                        name: 'Card Type',
                        outcomes: [
                            { id: 'yellow', name: 'Yellow Card', odds: 2.0 },
                            { id: 'red', name: 'Red Card', odds: 5.0 }
                        ]
                    }
                ]
            }
        },
        outcomes: [
            // IMPORTANT: The event.id, market.id, and outcome.id in each outcome object
            // MUST match the corresponding IDs provided in the 'args' parameter of the 'outcomes' request.
            // Only outcomes with matching IDs will be displayed in the widget.
            // If an outcome's IDs don't match any in args, it will be ignored.
            {
                // Response/data to outcome 0, if the id's wont match then they wont be displayed
                event: {
                    id: '51096239', // This needs to match eventId from one of the outcomes recieved in args!
                    date: '14/2/2025', // date of the event as it will be displayed, for tournament level chat
                    teams: [
                        {
                            id: '1w', // will be sent on click
                            name: 'Team A' // Home Team Name to be displayed
                        },
                        {
                            id: '2w', // will be sent on click
                            name: 'Team B' // Away team name to be displayed
                        }
                    ],
                    liveCurrentTime: 'First half', //  current time/period in the event that will be display after date in tournament level chat
                    isLive: true, // Will display LIVE indicator for tournament level chat
                    sport: {
                        // not used in VS, will be sent on click
                        id: '3d', // will be sent on click
                        name: 'Soccer' // sport name
                    },
                    category: {
                        // not used in VS, will be sent on click
                        id: '2f', // will be sent on click
                        name: 'U18' // category of sport event
                    },
                    tournament: {
                        // not used in VS, will be sent on click
                        id: '123g', // will be sent on click
                        externalId: '1a', // will be sent on click
                        name: 'Tournament A' // tournament name
                    },
                    result1: {
                        result: [1, 3] // home team score, away team score - displayed in tournament level chat
                    }
                },
                market: {
                    id: 19, // This needs to match marketId from one of the outcomes recieved in args!
                    // specifier: {
                    //     value: 'total=0.5', // This needs to match specifier from one of the outcomes recieved in args!
                    //     displayValue: 'Total Goals Over 1.5' // displayed in the widget
                    // },
                    externalId: '23d', // will be sent on click
                    name: 'Over/Under', // displayed in the widget
                    status: {
                        isActive: true
                    }
                },
                outcome: {
                    id: 12, // This needs to match outcomeId from one of the outcomes recieved in args!
                    externalId: '12a', // will be sent on click
                    name: 'Over 1.5', // displayed in the widget
                    oddsDecimal: 2.05, // if odds field not provided, this will be used to display odds, if odds field is provided this is used to display changes in odds
                    odds: '1/2', // if provided, odds will be displayed like this
                    status: {
                        isActive: true // if false, it will display "temporarily unavailable" instead of odds
                    },
                    isSelected: true
                }
            },
            {
                // Response/data to outcome 0, if the id's wont match then they wont be displayed
                event: {
                    id: '51096239', // This needs to match eventId from one of the outcomes recieved in args!
                    date: '14/2/2025', // date of the event as it will be displayed, for tournament level chat
                    teams: [
                        {
                            id: '1w', // will be sent on click
                            name: 'Team A' // Home Team Name to be displayed
                        },
                        {
                            id: '2w', // will be sent on click
                            name: 'Team B' // Away team name to be displayed
                        }
                    ],
                    liveCurrentTime: 'First half', //  current time/period in the event that will be display after date in tournament level chat
                    isLive: true, // Will display LIVE indicator for tournament level chat
                    sport: {
                        // not used in VS, will be sent on click
                        id: '3d', // will be sent on click
                        name: 'Soccer' // sport name
                    },
                    category: {
                        // not used in VS, will be sent on click
                        id: '2f', // will be sent on click
                        name: 'U18' // category of sport event
                    },
                    tournament: {
                        // not used in VS, will be sent on click
                        id: '123g', // will be sent on click
                        externalId: '1a', // will be sent on click
                        name: 'Tournament A' // tournament name
                    },
                    result1: {
                        result: [1, 3] // home team score, away team score - displayed in tournament level chat
                    }
                },
                market: {
                    id: 1, // This needs to match marketId from one of the outcomes recieved in args!
                    // specifier: {
                    //     value: 'total=0.5', // This needs to match specifier from one of the outcomes recieved in args!
                    //     displayValue: 'Total Goals Over 1.5' // displayed in the widget
                    // },
                    externalId: '23d', // will be sent on click
                    name: 'Over/Under', // displayed in the widget
                    status: {
                        isActive: true
                    }
                },
                outcome: {
                    id: 1, // This needs to match outcomeId from one of the outcomes recieved in args!
                    externalId: '12a', // will be sent on click
                    name: 'Over 1.5', // displayed in the widget
                    oddsDecimal: 2.05, // if odds field not provided, this will be used to display odds, if odds field is provided this is used to display changes in odds
                    odds: '1/2', // if provided, odds will be displayed like this
                    status: {
                        isActive: true // if false, it will display "temporarily unavailable" instead of odds
                    }
                }
            }
            // {
            //      reapeat for each outcome recieved in args that you want to display
            //      same format as above, needs to match another outcome recieved in args
            // }
            // If you don't wish to show outcomes recieved in args, then don't include them in the response.
        ]
    };

    /**
     * Adapter request handler - replace mock data with real API calls
     * @param {string} requestName - Type of data being requested
     * @param {object} args - Request-specific arguments
     * @param {function} callback - Callback to execute with data or errors
     * @returns {function|undefined} Unsubscribe function for real-time updates
     */
    function onRequest(requestName, args, callback) {
        switch (requestName) {
            case 'virtualStadium.betShare':
                // TODO: Replace with real API call
                // fetch('/api/bets/shared', {
                //     method: 'GET',
                //     headers: { 'Authorization': `Bearer ${userToken}` }
                // })
                // .then(response => response.json())
                // .then(data => callback(null, data))
                // .catch(error => callback(error));

                // Mock response for now
                setTimeout(() => {
                    callback(null, { bets: mockData.betShare });
                }, 100);
                break;

            case 'virtualStadium.matchEventMarkets':
                // Return markets for the requested event types
                const response = {};
                response.matchEventMarkets = mockData.matchEventMarkets;
                callback(response);
                break;
            case 'outcomes':
                // Return outcomes for Bet Insights
                // Filter outcomes to only include those that match the IDs in args.outcomes
                console.log("Outcomes request args:", args);

                if (args.outcomes && Array.isArray(args.outcomes)) {
                const requestedOutcomes = args.outcomes;
                const matchingOutcomes = mockData.outcomes.filter(mockOutcome => {
                    return requestedOutcomes.some(requested => {
                    return (
                        mockOutcome.event.id === requested.eventId &&
                        mockOutcome.market.id === requested.marketId &&
                        mockOutcome.outcome.id === requested.outcomeId
                    );
                    });
                });

                console.log("Returning matching outcomes:", matchingOutcomes);
                callback(null, { outcomes: matchingOutcomes });
                } else {
                // If no args.outcomes provided, return all mock outcomes
                callback(null, { outcomes: mockData.outcomes });
                }
                break;
            default:
                break;
        }
    }

    // Register adapter before adding widgets
    SIR('registerAdapter', onRequest);

    // Add Virtual Stadium widget
    SIR('addWidget', '.sr-widget', 'virtualstadium', {
        jwt: 'your-jwt-token', // TODO: Replace with real JWT token
        channelId: 'your-channel-id' // TODO: Replace with real channel ID
        enableBetShare: true,
        enableFlashBet: true,
        enableBetInsights: true,
    });
</script>

<!-- Widget container -->
<div class="sr-widget">Widget should load here.</div>
Last updated about 1 month ago
Is this site helpful?
Virtual Stadium, Moderation, Engagement Tools
Bet ShareFeatures
On this page
  • Prerequisites
  • Implementation Timeline
  • Sportradar's Responsibility
  • Your Responsibility
  • Phase 1: Adapter Registration
  • Adapter Function
  • Phase 2: Module Development
  • Next Steps