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

Bet Insights

Bet Insights provides betting trends, popular bets, and analytics to help users make informed betting decisions. The module displays real-time insights and suggestions powered by Sportradar data.

warning

This module relies on Sportradar Unified Odds Feed (UOF) market data. If you're not using Sportradar UOF markets, you will need to map your own market and outcome identifiers to the corresponding Sportradar format.


#Overview

Bet Insights shows users betting trends, popular bets, and analytics based on aggregated betting data and Sportradar's intelligent suggestions.

Bet Insights Demo

#Implementation

For custom adapter implementations, register your adapter using the SIR('registerAdapter', ...) method with a function that handles bet insights requests.

#Request Handler

requestName string required

The request type identifier. For Bet Insights, this will be 'outcomes'.

args OutcomesRequest required

Request parameters containing outcome insights data. See OutcomesRequest object for details.

callback function optional

Callback function to return outcome data in the format (error, response) => void.

Callback Arguments:

  • error Error | null - Error object if request failed, otherwise null
  • response OutcomesResponse | undefined - Response object containing outcomes array. See OutcomesResponse object for details.

Self-Service Adapter - Bet Insights Handler:

javascript
const onRequest = function onRequest(requestName, args, callback) {
    if (requestName === 'outcomes') {
        // Store callback for future updates
        lastOutcomesCallbackFn = callback;

        const response = {
            outcomes: args.outcomes.map(insight => {
                // Map Sportradar insight to your system
                return {
                    event: {
                        id: insight.eventId,
                        date: '14/2/2025',
                        teams: [
                            {
                                id: 'home_team_id',
                                name: 'Home Team'
                            },
                            {
                                id: 'away_team_id',
                                name: 'Away Team'
                            }
                        ],
                        liveCurrentTime: 'First half',
                        isLive: true,
                        sport: {
                            id: 'sport_1',
                            name: 'Soccer'
                        },
                        category: {
                            id: 'category_u18',
                            name: 'U18'
                        },
                        tournament: {
                            id: 'sr_tournament_id',
                            externalId: 'your_tournament_id',
                            name: 'U18 World Cup'
                        },
                        result1: {
                            result: [1, 3]
                        }
                    },
                    market: {
                        id: insight.marketId,
                        specifier: {
                            value: insight.specifier?.value || 'total=1.5',
                            displayValue: 'Total Goals Over 1.5'
                        },
                        externalId: 'your_market_id',
                        name: 'Over/Under',
                        status: {
                           status: 'active'
                        }
                    },
                    outcome: {
                        id: insight.outcomeId,
                        externalId: 'your_outcome_id',
                        name: 'Over 1.5',
                        oddsDecimal: 2.05,
                        odds: '1/2',
                        status: {
                            isActive: true
                        },
                        isSelected: false
                    }
                };
            })
        };

        callback(null, response);
        
        return function unsubscribe() {
            // Cleanup logic
        };
    }
};

SIR('registerAdapter', onRequest);

#OutcomesRequest Object

The OutcomesRequest object contains Sportradar's insight suggestions that need to be mapped to your betting system.

#OutcomesRequest Properties

outcomes OutcomeInsight[] required

Array of outcome insight objects that Sportradar has generated suggestions for. Each insight contains identifiers to match against your betting data.

OutcomesRequest Example:

javascript
{
    outcomes: [
        {
            eventId: '51099405',
            marketId: 19,
            outcomeId: 13,
            specifier: {
                value: 'total=0.5'
            }
        },
        {
            eventId: '51099405',
            marketId: 1,
            outcomeId: 3
        }
    ]
}

#OutcomeInsight Object

The OutcomeInsight object represents a single betting insight suggestion from Sportradar.

#OutcomeInsight Properties

eventId string required

Sportradar event ID for the match.

marketId number required

Sportradar market ID.

outcomeId number required

Sportradar outcome ID.

specifier object optional

Market specifier details with a value property (e.g., "total=0.5").

OutcomeInsight Example:

javascript
{
    eventId: '51099405',
    marketId: 19,
    outcomeId: 13,
    specifier: {
        value: 'total=0.5'
    }
}

#OutcomesResponse Object

The response object returned to the callback containing mapped outcome data from your betting system.

#OutcomesResponse Properties

outcomes OutcomeData[] required

Array of outcome data objects containing event, market, and outcome information.

OutcomesResponse Example:

javascript
{
    outcomes: [
        {
            event: {
                id: '51099405',
                date: '14/2/2025',
                teams: [
                    { id: 'home_id', name: 'Home Team' },
                    { id: 'away_id', name: 'Away Team' }
                ],
                isLive: true,
                sport: { id: '1', name: 'Soccer' }
            },
            market: {
                id: 19,
                name: 'Over/Under',
                status: { isActive: true }
            },
            outcome: {
                id: 13,
                name: 'Over 0.5',
                oddsDecimal: 1.85,
                status: { isActive: true }
            }
        }
    ]
}

#OutcomeData Object

The OutcomeData object contains complete betting information for a single outcome insight.

#OutcomeData Properties

event Event required

Event information including teams, score, and tournament details.

market Market required

Market information including name, specifiers, and status.

outcome Outcome required

Outcome information including name, odds, and selection status.

OutcomeData Example:

javascript
{
    event: {
        id: '51099405',
        date: '14/2/2025',
        teams: [
            { id: 'home_id', name: 'Home Team' },
            { id: 'away_id', name: 'Away Team' }
        ],
        liveCurrentTime: 'First half',
        isLive: true,
        sport: { id: '1', name: 'Soccer' },
        category: { id: 'u18', name: 'U18' },
        tournament: {
            id: 'sr_tournament_id',
            name: 'U18 World Cup'
        },
        result1: { result: [1, 3] }
    },
    market: {
        id: 19,
        specifier: {
            value: 'total=1.5',
            displayValue: 'Total Goals Over 1.5'
        },
        externalId: 'your_market_id',
        name: 'Over/Under',
        status: { 
            status: 'active'
        }
    },
    outcome: {
        id: 13,
        externalId: 'your_outcome_id',
        name: 'Over 1.5',
        oddsDecimal: 2.05,
        odds: '1/2',
        status: { isActive: true },
        isSelected: false
    }
}

#Storing Callback for Updates

Store the callback reference to update selections when the user's bet slip changes. This allows you to reflect real-time updates in the Bet Insights widget.

#Usage

When a user adds or removes a selection from their bet slip, call the stored callback with updated outcome data where isSelected reflects the current state.

Callback Storage Example:

javascript
let lastOutcomesCallbackFn;

const onRequest = function onRequest(requestName, args, callback) {
    if (requestName === 'outcomes') {
        // Store callback for future updates
        lastOutcomesCallbackFn = callback;

        const response = {
            outcomes: args.outcomes.map(insight => ({
                // ... build response
            }))
        };

        callback(null, response);
        
        return function unsubscribe() {
            lastOutcomesCallbackFn = null;
        };
    }
};

// Later, when bet slip changes:
if (lastOutcomesCallbackFn) {
    const updatedResponse = {
        outcomes: [
            // Updated outcomes with isSelected status
        ]
    };
    lastOutcomesCallbackFn(null, updatedResponse);
}

#Mapping Non-Sportradar Markets

If you're not using Sportradar UOF markets, you need to create a mapping between your market/outcome IDs and Sportradar's standard IDs.

#Step 1: Understanding the Request

The adapter's onRequest function will receive:

  1. First argument: requestName with value 'outcomes'
  2. Second argument: args object containing an outcomes array

Each object in the outcomes array is of type OutcomeRequestEntry and contains Sportradar's market identifiers that you need to map to your system.

Properties explained:

  • eventId: Sportradar event ID
  • marketId: Sportradar market type ID (e.g., 20 = Total goals)
  • outcomeId: Sportradar outcome ID (e.g., 12 = Over)
  • specifier: Optional market parameters (e.g., total=1.5 for Over/Under 1.5 goals)

OutcomeRequestEntry Example:

javascript
{
    "eventId": "50850527",
    "marketId": 20,
    "outcomeId": 12,
    "specifier": {
        "value": "total=1.5"
    }
}

#Step 2: Map Event ID to Your Match

Map the Sportradar eventId to your corresponding event/match in your system.

  1. Look up this Sportradar event ID in your system
  2. Retrieve your internal match data
  3. Use the match information to find the corresponding markets

Event Mapping Example:

javascript
const onRequest = function(requestName, args, callback) {
    if (requestName === 'outcomes') {
        const response = {
            outcomes: args.outcomes.map(insight => {
                // Step 2: Map Sportradar eventId to your match
                const sportradarEventId = insight.eventId; // "50850527"
                
                // Look up your match using Sportradar event ID
                const yourMatch = findMatchBySportradarEventId(sportradarEventId);
                // Example: { id: 'match_123', name: 'Team A vs Team B', ... }
                
                if (!yourMatch) {
                    console.warn(`No match found for eventId: ${sportradarEventId}`);
                    return null;
                }
                
                // Continue with market mapping in next steps...
                return {
                    event: {
                        id: sportradarEventId, // Keep Sportradar ID
                        externalId: yourMatch.id, // Your internal ID
                        // ... more event data
                    },
                    // ... market and outcome data
                };
            }).filter(Boolean) // Remove null entries
        };
        
        callback(null, response);
        return () => {};
    }
};

#Step 3: Find Market Definition

Look up the Sportradar marketId to understand the market type and its parameters.

In this example, "marketId": 20 can be found in the All Soccer Markets documentation.

Understanding Market Templates:

Market names may contain placeholders that need to be replaced with actual team names:

  • {$competitor1} = HOME team name
  • {$competitor2} = AWAY team name

Once you've identified the match from Step 2, you know the home and away team names. Use these to replace the placeholders in the market template.

For example, if the market template is "{$competitor2} total" and the away team is "Team B", the final market name becomes "Team B total".

Market Lookup Example:

javascript
// Market ID 20 definition from documentation
const marketDefinitions = {
    20: {
        name: '{$competitor2} total', // Template with placeholder
        description: 'Total goals scored by away team',
        // ... other market properties
    },
    // ... other markets
};

// Step 3: Find and process market definition
const marketDef = marketDefinitions[insight.marketId]; // marketId: 20

if (!marketDef) {
    console.warn(`Unknown marketId: ${insight.marketId}`);
    return null;
}

// Replace placeholders with actual team names from your match
const marketName = marketDef.name
    .replace('{$competitor1}', yourMatch.homeTeam.name) // "Team A"
    .replace('{$competitor2}', yourMatch.awayTeam.name); // "Team B"

// Example result: "Team B total"

// Now find this market in your system
const yourMarket = findMarketByNameAndSpecifier(
    yourMatch.id,
    marketName,
    insight.specifier?.value // "total=1.5"
);

#Step 4: Complete Market Name With Outcome

In some cases, the market name requires the outcome information to be complete. Look up the outcomeId in the same market documentation.

In this example, "outcomeId": 12 has the outcome name "over {total}".

Combining the market name from Step 3 with the outcome name gives us: "Team B total – over {total}"

This market name still contains a placeholder {total} that will be replaced in the next step using the specifier value.

Outcome Lookup Example:

javascript
// Market and outcome definitions
const marketDefinitions = {
    20: {
        name: '{$competitor2} total',
        outcomes: {
            12: { name: 'over {total}' },
            13: { name: 'under {total}' },
            // ... other outcomes
        }
    }
};

// Step 4: Get outcome name and combine with market name
const marketDef = marketDefinitions[insight.marketId]; // 20
const outcomeDef = marketDef.outcomes[insight.outcomeId]; // 12

if (!outcomeDef) {
    console.warn(`Unknown outcomeId: ${insight.outcomeId}`);
    return null;
}

// Build complete market name with outcome
let marketName = marketDef.name
    .replace('{$competitor1}', yourMatch.homeTeam.name)
    .replace('{$competitor2}', yourMatch.awayTeam.name); // "Team B total"

const fullMarketName = `${marketName} - ${outcomeDef.name}`;
// Result: "Team B total - over {total}"

// Still has {total} placeholder to be replaced with specifier

#Step 5: Replace Specifier Placeholders

The {total} placeholder matches the specifier name, which means it should be replaced with the value from the specifier.

Extract the value from the specifier object and replace the placeholder in the outcome name:

json
"specifier": {
    "value": "total=1.5"
}

Parse the specifier value string to extract "1.5" and replace {total} with it.

This makes the final outcome name 'over 1.5'.

info

In this example, outcome ID 12 will always represent the 'Over' market. The specifier value determines the specific threshold (e.g., 1.5, 2.5, etc.).

Specifier Replacement Example:

javascript
// Parse specifier value (format: "key=value")
const specifierValue = insight.specifier?.value; // "total=1.5"
const specifierPairs = {};

if (specifierValue) {
    // Split by '|' for multiple specifiers, then by '=' for key-value pairs
    specifierValue.split('|').forEach(pair => {
        const [key, value] = pair.split('=');
        specifierPairs[key] = value;
    });
}
// Result: { total: "1.5" }

// Replace placeholders in outcome name
let finalOutcomeName = outcomeDef.name; // "over {total}"

// Replace each specifier placeholder
Object.keys(specifierPairs).forEach(key => {
    const placeholder = `{${key}}`;
    finalOutcomeName = finalOutcomeName.replace(placeholder, specifierPairs[key]);
});

// Result: "over 1.5"

// Now you can search for this exact market and outcome in your system
const yourOutcome = findOutcomeByName(
    yourMatch.id,
    marketName,
    finalOutcomeName
);

console.log(finalOutcomeName); // "over 1.5"

#Step 6: Build the OutcomeResponseEntry

Now that you have your market, it's time to build the OutcomeResponseEntry for this suggestion.

Important: Properties highlighted in the response example must match with the object that was given in the request for the suggestion to show:

json
{
    "eventId": "50850527",
    "marketId": 20,
    "outcomeId": 12,
    "specifier": {
        "value": "total=1.5"
    }
}

The response event.id, market.id, outcome.id, and market.specifier.value must match the request values exactly.

tip

If you do not want to show a suggestion, simply do not return a response entry for it (return null or filter it out).

OutcomeResponseEntry Example:

javascript
{
    "event": {
        "id": "50850527", // Must match request eventId
        "date": "2023-07-18",
        "teams": [
            {
                "name": "Team A"
            },
            {
                "name": "Team B"
            }
        ],
        "liveCurrentTime": "12:30",
        "isLive": true,
        "sport": {
            "id": "1",
            "name": "Soccer"
        },
        "category": {
            "id": "sr:category:31",
            "name": "Location"
        },
        "tournament": {
            "id": "sr:tournament:1",
            "name": "Mock League"
        },
        "result1": {
            "result": [1, 0]
        }
    },
    "market": {
        "id": 20, // Must match request marketId
        "name": "Market 19 (marketId: 20, specifier: total=1.5)",
        "status": {
            "status": "active"
        },
        "specifier": {
            "value": "total=1.5" // Must match request specifier.value
        }
    },
    "outcome": {
        "id": 12, // Must match request outcomeId
        "name": "Outcome 19 (outcomeId: 12)",
        "status": {
            "isActive": true
        },
        "competitor": "Competitor 19 (eventId: 50850527)",
        "oddsDecimal": 1.37,
        "odds": "1.37"
    }
}

#Displayed Data Structure

#Teams

When building your response, the teams array is used to replace placeholders in market names.

Bet Insights Team Data

This data is displayed in the Bet Insights widget as shown in the example, where Team A and Team B are clearly identified along with their statistics.

Teams Array Structure:

javascript
"teams": [
    {
        "name": "Team A"  // Home team - first item
    },
    {
        "name": "Team B"  // Away team - second item
    }
]
warning

The order matters - home team must be the first item and away team must be the second item in the teams array.

#Match Status and Score Display

The following event properties are displayed at the tournament level in the Bet Insights widget:

  • liveCurrentTime: Current match time or period (e.g., "12
    ", "First half")
  • isLive: Boolean indicating if the match is currently live
  • result1: Current score with result array containing [home score, away score]
Bet Insights Team Data

These fields provide real-time context for the betting suggestion, helping users understand the current state of the match.

Match Status Properties:

javascript
"liveCurrentTime": "12:30",
"isLive": true,
"result1": {
    "result": [1, 0]  // [home score, away score]
}

#Sport and Category (Not Currently Displayed)

While sport and category are included in the event object structure, they are currently not displayed in the Bet Insights widget.

These fields should still be provided in the response for potential future use and to maintain a complete data structure.

Sport and Category Properties:

javascript
"sport": {
    "id": "1",
    "name": "Soccer"
},
"category": {
    "id": "sr:category:31",
    "name": "Location"
}

#Tournament Display

The tournament object is displayed in the Bet Insights widget.

Bet Insights Team Data

Tournament Properties:

javascript
"tournament": {
    "id": "sr:tournament:1",
    "name": "Mock League"
}

#Market and Outcome Display

The market and outcome data is displayed within each bet insight card:

  • market.name: Displayed at the top of the insight card
  • outcome.name: Displayed in the button before the odds
  • odds: The odds value shown to the user
Bet Insights Team Data

#Odds Format Handling

You have flexibility in how to provide odds:

  • oddsDecimal: If provided, the system will handle odds format conversion based on user preferences (decimal, fractional, American). This value is also used for indicator arrows when odds change (note: odds change indicators are not currently supported in the insights widget).
  • odds: If provided, the odds will be displayed exactly as given without any format conversion.
  • Priority: If both oddsDecimal and odds are provided, oddsDecimal will be used for the change indicator functionality, while odds determines the display format.

Market and Outcome Display Example:

javascript
{
    "market": {
        "name": "Market 17 (marketId: 18, specifier: total=3.5)",
    },
    "outcome": {
        "name": "Outcome 17 (outcomeId: 12)",
        "oddsDecimal": 1.72,  // Used for format conversion and change indicators
        "odds": "1.72"        // Used for display as-is
    }
}
tip

Odds Handling Best Practice

  • Use oddsDecimal if you want automatic odds format conversion
  • Use odds if you want to control the exact display format
  • Provide both if you want to control display while enabling change detection

#Market and Outcome Status

The status.status (for market) and status.isActive (for outcome) property controls the availability of markets and outcomes in the Bet Insights widget. The possible values for market status are: active, deactivated, suspended, settled and cancelled.

Bet Insights Team Data

When status.isActive is set to false, the outcome becomes temporarily unavailable and unselectable by the user. This is useful for:

  • Suspending markets during critical match moments
  • Temporarily disabling outcomes that are under review
  • Managing outcome availability based on your trading rules

Both market-level and outcome-level status can be controlled independently.

Status Control Example:

javascript
{
    "market": {
        "status": {
            "status": 'suspended'  // Market is suspended
        }
    },
    "outcome": {
        "status": {
            "isActive": false  // Outcome is unavailable
        }
    }
}
warning

When isActive is false, the outcome will be visually indicated as unavailable and users will not be able to select it for betting.


#Complete Implementation

Here's the complete implementation combining all steps to map Sportradar insights to your system and build the response.

Complete Data Example:

javascript
{
    event: {
        id: "50850527",
        date: "2023-07-18",
        teams: [            {
                name: "Team A"
            }, {
                name: "Team B"
            }
        ],
         tournament: {
            id: "sr:tournament:1",
            name: "The League"
        },
        result1: {
            result: [1, 0]
        }
    },
    market: {
        id: 20,
        name: "Total",
        status: {
            status: 'active' | 'deactivated' | 'suspended' | 'settled' | 'cancelled'
        },
        specifier: {
            value: "total=1.5"
        }
    },
    outcome: {
        id: 12,
        name: "Over 3.5",
        status: {
            isActive: true
        },        
        oddsDecimal: 1.37,
        odds: "1.36"
    }
}

Complete Mapping Example:

javascript
const onRequest = function(requestName, args, callback) {
    if (requestName === 'outcomes') {
        const response = {
            outcomes: args.outcomes.map(insight => {
                // Step 2: Get match
                const match = findMatchBySportradarEventId(insight.eventId);
                
                if (!match) {
                    return null; // Don't show this suggestion
                }
                
                // Step 3: Get market definition and replace team placeholders
                const marketDef = marketDefinitions[insight.marketId];
                let marketName = marketDef.name
                    .replace('{$competitor1}', match.homeTeam.name)
                    .replace('{$competitor2}', match.awayTeam.name);
                
                // Step 4: Get outcome name
                const outcomeDef = marketDef.outcomes[insight.outcomeId];
                
                // Step 5: Parse and replace specifier placeholders
                const specifierPairs = {};
                if (insight.specifier?.value) {
                    insight.specifier.value.split('|').forEach(pair => {
                        const [key, value] = pair.split('=');
                        specifierPairs[key] = value;
                    });
                }
                
                let outcomeName = outcomeDef.name;
                Object.keys(specifierPairs).forEach(key => {
                    outcomeName = outcomeName.replace(`{${key}}`, specifierPairs[key]);
                });
                
                // Find this in your system
                const yourOutcome = findYourOutcome(
                    match.id,
                    marketName,
                    outcomeName
                );
                
                if (!yourOutcome) {
                    return null; // Don't show if not found
                }
                
                // Step 6: Build OutcomeResponseEntry
                return {
                    event: {
                        id: insight.eventId, // MUST match request
                        date: match.date,
                        teams: match.teams,
                        liveCurrentTime: match.liveTime,
                        isLive: match.isLive,
                        sport: match.sport,
                        category: match.category,
                        tournament: match.tournament,
                        result1: { result: match.score }
                    },
                    market: {
                        id: insight.marketId, // MUST match request
                        name: yourOutcome.marketName,
                        status: { isActive: yourOutcome.isActive },
                        specifier: {
                            value: insight.specifier?.value // MUST match request
                        }
                    },
                    outcome: {
                        id: insight.outcomeId, // MUST match request
                        name: yourOutcome.name,
                        status: { isActive: yourOutcome.isActive },
                        oddsDecimal: yourOutcome.odds,
                        odds: yourOutcome.odds.toString()
                    }
                };
            }).filter(Boolean) // Remove null entries
        };
        
        callback(null, response);
        return () => {};
    }
};
Last updated 4 days ago
Is this site helpful?
Virtual Stadium, Moderation, Engagement Tools, BET
Flash ShareTheming
On this page
  • Overview
  • Implementation
  • Request Handler
  • OutcomesRequest Object
  • OutcomesRequest Properties
  • OutcomeInsight Object
  • OutcomesResponse Object
  • OutcomesResponse Properties
  • OutcomeData Object
  • Storing Callback for Updates
  • Usage
  • Mapping Non-Sportradar Markets
  • Step 1: Understanding the Request
  • Step 2: Map Event ID to Your Match
  • Step 3: Find Market Definition
  • Step 4: Complete Market Name With Outcome
  • Step 5: Replace Specifier Placeholders
  • Step 6: Build the OutcomeResponseEntry
  • Displayed Data Structure
  • Teams
  • Match Status and Score Display
  • Sport and Category (Not Currently Displayed)
  • Tournament Display
  • Market and Outcome Display
  • Market and Outcome Status
  • Complete Implementation