Skip to main content
Logo
Explore APIsSupport Portal
  • Home
  • Match Preview
  • Tournament Preview
  • Virtual Stadium
  • StatsHub
  1. Engagement Tools
  2. Bet Insights

Bet Insights

Widgets, Engagement Tools, BET
World CupTournament Bet Insights
Last updated 9 days ago
Is this site helpful?
On this page
  • Supported Content and Environment
  • Required Parameters
  • Adapter
  • Adapter Endpoint Requirements
  • Adapter Implementation Template
  • Main Configurable Features
  • Integration Mode
  • Widget Title
  • Card Variants
  • Outcome Position
  • Cards Layout
  • Pop-up Direction
  • Widget Header Options
  • API Reference
  • Integration Process
  • Communication Channel Setup
  • Technical Kickoff Meeting
  • Basic Integration Example
  • Advanced Integration
  • Handling user selections
  • Bet Slip Integration
  • Hooking Widget Events into the Bet Slip
  • Market and Outcome Mapping Example
  • Widget Behavior
  • Flow Diagram
  • Market status
  • Custom Theming
  • Tips
  • Choose Appropriate Integration Mode
  • Related Resources

Bet Insights widget displays AI-powered betting insights and recommendations for specific markets within a match. The widget analyzes betting patterns, historical data, and statistical trends to highlight valuable betting opportunities with detailed market and outcome information. It provides intelligent suggestions to help users identify potentially profitable bets based on comprehensive data analysis. The widget offers flexible integration modes including inline display for dedicated sections or button-triggered modal for compact placements, multiple card layout variants for different use cases, and extensive customization options for visual presentation and user interaction patterns.

Unified odds feed (UOF) mapping required

This widget requires Sportradar Unified Odds Feed (UOF) identifiers to correctly match insights to your offering and odds. If your platform does not use UOF IDs you will need to map UOF identifiers to your own market/outcome IDs. Mapping UOF to proprietary identifiers is complex and is difficult to achieve full coverage across all markets and specifier variants — expect limited support.

Warning — this widget isn't suited with declarative integration

This widget requires the onItemClick callback to communicate user interactions with your application. Because HTML declarative integration cannot provide callback functions, the declarative method is not suited for this widget — use the JavaScript/programmatic integration shown in the examples below.

Bet Insights Web Inline

See the Bet Insights widget demo.

#Supported Content and Environment

#Required Parameters

  • widget-name: betInsights

Required identifiers:

  • matchId: The Sportradar match identifier. See Getting Identifiers

Environment Requirements

Technical Requirements:

  • JavaScript enabled
  • XMLHttpRequest support for data fetching
  • CSS3 support for styling and animations

Supported Sports

  • Soccer

Supported Languages

Expanded Table

Supported Markets

Soccer

Expanded Table

#Adapter

For implementation guidance, example adapter implementations, and the adapter endpoint contract required by this widget, see the Adapter overview. It explains how to choose between Generic-Sportradar, custom-mapping, or self-hosted adapters and includes sample payloads you can adapt.

warning

Betting insights rely on Sportradar's Unified Odds Feed (UOF) market and outcome identifiers. You must map those identifiers to your application's market/outcome IDs. While simple market mapping can be achieved for common markets, achieving complete coverage across all markets and variants is difficult.

See the Market and outcome mapping example for mapping guidance and examples.

#Adapter Endpoint Requirements

Mandatory Endpoints
  • eventMarkets or (market + availableMarketsForEvent)
  • event
Optional Endpoints
  • betSlipSelection - visual representation of markets already in betslip

#Adapter Implementation Template

This is an example of an adapter implementing all endpoints. It is intended to be a copy/paste template, where only data fetching and transformation need to be implemented. When implementing an adapter, implement only the endpoints which are required by the widget being integrated, and discard the rest. For each endpoint, only the getData${ENDPOINT_NAME}() and transormData${ENDPOINT_NAME}() functions need to be implemented.

Expand Adapter Template Code

html

#Main Configurable Features

#Integration Mode

The widget can be embedded inline (always visible) or as a button that opens a pop-up overlay.

Inline Integration

Always-visible insights displayed directly on the page — ideal for dedicated betting sections.

javascript
{

#Widget Title

The title in the widget header (or button label) can include or hide its icon.

Title with Icon
javascript
{ widgetTitle: "Bet Insights" }

#Card Variants

Controls the visual style of each insight card.


#Outcome Position

Controls where the outcome name appears relative to the odds on each card.

Expanded Table

PositionConfigDescription
Bottom (default)outcomeButtonPosition: "bottom"Outcome name below the odds
TopoutcomeButtonPosition: "top"Outcome name above the odds

#Cards Layout

Vertical Layout

Cards stacked vertically — suitable for narrow containers and sidebars.

javascript
{

#Pop-up Direction

When using button integration (integration: "button"), controls which direction the pop-up opens.

Pop-up Left
javascript

#Widget Header Options

Header Disabled

Hides the widget header entirely in inline mode.

javascript

See the Bet Insights demo for live examples.

#API Reference

Expanded Table

#Integration Process

This section outlines the process required to integrate the Bet Insights widget into your platform.

#Communication Channel Setup

Your sales contact person will initiate a shared communication channel via Slack invite to facilitate real-time collaboration between teams. This channel serves as the primary medium for answering questions, exchanging required information, and providing ongoing integration support.

#Technical Kickoff Meeting

Your sales contact person will organize a kickoff meeting with your technical team and Sportradar's integration team. This meeting serves as the formal start of the technical integration process and ensures both teams are aligned on requirements, timelines, and next steps. During the kickoff the teams will decide the adapter implementation type — this is important because Sportradar needs to confirm whether we have access to your odds and you can use generic adapter or if your platform uses Unified Odds Feed (UOF) identifiers. If your platform does not use UOF IDs, you may need to implement a custom adapter on your side; that decision can affect timeline, required effort, and the level of market coverage achievable.

#Basic Integration Example

Properties do not always transfer from the above table directly into integration code. Properties must be transformed differently for each integration method:

JavaScript/Programmatic Integration

  • Property names remain unchanged in camelCase
  • Properties become members of the 4th parameter object in SIR() call
  • Example: cardVariant: "compact"
info

In javascript integration, the properties go into an object which is passed as the 4th argument of the call ti SIR() function. Please see Global SIR API

HTML/Declarative Integration

  • Convert camelCase to lowercase with dashes, e.g. cardVariant becomes card-variant
  • Add data-sr- prefix
  • Example: cardVariant →

To run this widget you must provision an adapter that supplies match and market data. The best adapter type depends on how your data is structured and delivered (for example: a Generic-Sportradar adapter, a custom mapping adapter, or a self-hosted adapter). During onboarding our team will review your data, recommend the optimal adapter, and assist with configuration. After the adapter type is chosen, use the matching example below for the correct integration and configuration steps.

Learn more about adapter options here.

Important

Replace <CLIENT_ID> and <DATA_SOURCE> with the values provided during onboarding (for example client id: client1, data source: spider). In code use adapterDataSource: 'spider' and https://widgets.sir.sportradar.com/client1/widgetloader.

#Advanced Integration

#Handling user selections

The following example shows how to respond when a user clicks an insight card. The onItemClick callback receives the click target and the outcome data, which you can use to build a selection object and push it into your bet slip. Walk through the highlighted sections to understand each part of the flow.

#Bet Slip Integration

The following example shows how to connect the widget to your bet slip so it always reflects the punter's current selections. The adapter's betSlipSelection endpoint keeps the widget in sync, and onItemClick lets you act when the user clicks an outcome card. Walk through the highlighted sections to understand each part of the flow.

We recommend revalidating each selection and refreshing odds when adding them to the bet slip — or at minimum revalidating selections and odds immediately before accepting a bet — to ensure markets are still available and the odds presented to the user are up to date.

javascript
{
    onItemClick: function(event, outcomeData) {
        // Add to bet slip
        betSlip.addSelection({
            matchId: outcomeData.matchId,
            marketId: outcomeData.market.id,
            outcomeId: outcomeData.outcome.id,
            odds: outcomeData.




#Hooking Widget Events into the Bet Slip

The following example brings together the widgetloader, the adapter's betSlipSelection endpoint, and the onItemClick handler into a single ready-to-use snippet. Replace the placeholder values.

Important

Replace <CLIENT_ID> and <DATA_SOURCE> with the values provided during onboarding (for example client id: client1, data source: spider). In code use adapterDataSource: 'spider' and https://widgets.sir.sportradar.com/client1/widgetloader.

html
<





























































#Market and Outcome Mapping Example

The widget uses Sportradar Unified Odds Feed (UOF) market and outcome identifiers internally. When building a self-hosted adapter, you must map these identifiers to your own market/outcome IDs so the widget can display correct names, odds, and statuses.

To help verify your mapping, the widget provides a testMarkets prop that generates mocked markets and outcomes for a given matchId. This lets you confirm that every market renders correctly before you connect live data.

#Simple format

For quick testing, pass a pipe-delimited list of market IDs. Predefined specifiers are added automatically where needed:

javascript
SIR('addWidget', '#bet-insights', 'betInsights', {
    matchId: 61591316,
    testMarkets: "7|8"
});

This generates mocked insights for market 7 and market 8 with their default specifiers.

#Advanced format with specifiers

When you need to test specific specifier values (or multiple values for the same market), use the advanced string format:

text
marketId1;specifier1,specifier2|marketId2;specifier1,specifier2

Delimiters:

Expanded Table

DelimiterPurpose
|Separates multiple market configurations
;Separates market ID from its specifier sets
,Separates multiple specifier sets for one market
&Separates multiple specifiers within the same set (for markets that use more than one specifier)

#Specifier examples

A specifier is a key=value pair. Separate multiple specifier sets with a comma:

javascript
SIR('addWidget', '#bet-insights', 'betInsights', {
    matchId: 61591316,
    testMarkets: "7;score=1:0,score=1:1|8;total=0.5,total=1.5"
});

This generates mocked insights for:

  • Market 7 with specifier score=1:0
  • Market 7 with specifier score=1:1
  • Market 8 with specifier total=0.5
  • Market 8 with specifier total=1.5

#Multiple specifiers in a single set

Some markets require more than one specifier. Use & to combine them within one set.

For example, market 1183 ("{player} total shots (inc. overtime)") uses both a total and a player specifier:

javascript
SIR('addWidget', '#bet-insights', 'betInsights', {
    matchId: 61591316,
    testMarkets: "1183;total=0.5&player=sr:player:1234,total=1.5&player=sr:player:2345"
});

This generates mocked insights for:

  • Market 1183 with specifiers total=0.5 and player=sr:player:1234
  • Market 1183 with specifiers total=1.5 and player=sr:player:2345
info

At the moment Bet Insights does not support any market with multiple specifiers in production, but the testMarkets prop accepts them for forward-compatibility testing.

#Full test-page example

The following snippet loads the widgetloader, registers a self-hosted mocked adapter, and requests testMarkets for markets 1 (1x2), 18 (Total) and 8 ({!goalnr} goal). The adapter only returns markets 1 and 18, so the widget will display only those — with market 18 using the total=1.5 specifier.

html
Important

Once you verify that the mocked markets render correctly, remove the testMarkets prop and connect your adapter to live data.

#Widget Behavior

The following describes how the Bet Insights widget reacts to data, market and outcome state changes, and user interactions. It covers visibility rules, how selections and bet-slip events are handled, and how adapter updates or error states affect the widget UI and available actions.

#Flow Diagram

Bet Insights Integration Data Flow

The diagram above illustrates the complete data lifecycle for the Bet Insights widget, which runs in the end user's browser and coordinates data between Sportradar and your systems:

  1. Insights Generation (1): AI/ML models in the Sportradar API produce market insights and deliver them to the widget.
  2. Market Data Resolution (2–4): The widget requests available markets and odds from your Client API via the Adapter; the Adapter transforms and returns matching available market data.
  3. User Interaction (5–7): The end user clicks an outcome (5); the widget invokes the configured onItemClick handler (6) so your app can add the selection to the bet slip (7).
  4. State Synchronization (8–9): Your Adapter exposes a betSlipSelection subscription/callback that the widget registers; when your bet slip changes the Adapter invokes the callback (8–9) and the widget updates its UI to reflect current selections.

#Market status

Market suspended example

When market status is set to suspended, the widget disables that market's outcomes and displays the label "temporary unavailable" in place of odds, indicating the market is temporarily not available for betting.

javascript
const market = {
    id: "1",
    name: "Match Winner (1X2)",
    outcomes: [
        { id: "1", name: "Home", status: "active", odds: { type: "eu", value: "2.10" } },
        { id: "X", name



If a market or outcome has any status other than active (for example: suspended or cancelled), the widget will hide that market to avoid showing stale or unavailable betting options.

#Custom Theming

Widget comes with pre-existing styling but can be customized by applying custom CSS properties to its different HTML elements. The widget's custom class selectors and supported CSS properties are listed below. Note that all custom classes must be nested within the .sr-bb.sr-<WIDGET_NAME> selector class. This ensures that the custom styles only apply to that widget and not to other elements on the page.

Important

Replace <WIDGET_NAME> with insights!

scss
.sr-bb.sr-insights {
    .srct-ins-button {
        border-radius: 2px;
    }
    .srct-ins-showmore {
        color: #4786ff;
    }
}

Expanded Table

#Tips

Practical integration, performance, and UX tips for using the Bet Insights widget effectively.

#Choose Appropriate Integration Mode

Select integration mode based on page layout and user experience goals:

#Dedicated betting insights section

Bet Insights Desktop Inline Placement
#1. High-Density Betting Hubs

If your page is already a "one-stop shop" (like the screenshot, featuring a Bet Slip, Live Table, and Match Odds), the inline mode is ideal. It allows the insights to sit natively within the layout hierarchy, making the analysis feel like a core part of the product rather than a pop-up or a sidebar afterthought.

#2. Reducing Friction in the "Live" Experience

When matches are live, every second counts.

  • Contextual Betting: By placing insights directly above the odds, you provide immediate justification for a bet.

  • Visual Flow: Users can read a trend (e.g., "Nice are leading by a goal... they've won 8 times") and immediately click the odds in the table below to add it to their Bet Slip.

#3. Optimizing for "Sticky" User Engagement

The inline mode is best used when you want the user to scroll and explore.

  • Horizontal Exploration: The carousel-style layout encourages users to swipe through different matches while staying on the same tournament page.

  • Secondary Content: It works perfectly as a "header" for specific leagues (like Ligue 1), providing a narrative summary before the user dives into the raw numbers of the matches below.

javascript
{

#Related Resources

Getting Identifiers

Learn how to obtain match IDs required for widget configuration.

Learn More

Global SIR API

Complete reference for the global SIR API function, including initialization, configuration options, and widget management methods

Learn More

Widget Theming

Customize widget appearance, colors, fonts, and styling to match your brand.

Learn More
CodeLanguageNative
sqiAlbanianShqip
aaArabicالعربية
hyeArmenianՀայերեն
azeAzerbaijaniAzərbaycan dili
bsBosnianBosanski
bgBulgarianБългарски
zhChinese (Simplified)简体中文
zhtChinese (Traditional)中文繁體
hrCroatianHrvatski
csCzechČesky
daDanishDansk
nlDutchNederlands
enEnglish
en_usEnglish (US)
etEstonianEesti
fiFinnishSuomeksi
frFrenchFrançais
kaGeorgianქართული
deGermanDeutsch
elGreekEλληνικά
hebHebrewעברית
hiHindiहिन्दी
huHungarianMagyar
islIcelandicÍslenska
idIndonesianBahasa Indonesia
itItalianItaliano
jaJapanese日本語
kmKhmerខ្មែរ
koKorean한국어
lvLatvianLatviešu
ltLithuanianLietuvių
mkMacedonianМакедонски
noNorwegianNorsk
plPolishPolski
ptPortuguesePortuguês
brPortuguese (Brazil)Português do Brasil
roRomanianRomână
ruRussianРусский
srSerbian (Cyrillic)Cрпски
srlSerbian (Latin)Srpski
skSlovakSlovenčina
slSlovenianSlovenščina
esSpanishEspañol
swSwahiliKiswahili
seSwedishSvenska
thThaiไทย
trTurkishTürkçe
tukTurkmen
ukrUkrainianУкраїнська
viVietnameseTiếng Việt
Market IDMarket Name
11x2
8{!goalnr} goal
9Last goal
10Double chance
11Draw no bet
12{$competitor1} no bet
13{$competitor2} no bet
14Handicap {hcp}
15Winning margin
16Handicap
18Total
19{$competitor1} total
20{$competitor2} total
21Exact goals
23{$competitor1} exact goals
24{$competitor1} exact goals
24{$competitor2} exact goals
25Goal range
26Odd/even
27{$competitor1} odd/even
28{$competitor2} odd/even
29Both teams to score
30Which team to score
31{$competitor1} clean sheet
32{$competitor2} clean sheet
33{$competitor1} win to nil
34{$competitor2} win to nil
351x2 & both teams to score
36Total & both teams to score
371x2 & total
38{!goalnr} goalscorer
39Last goalscorer
40Anytime goalscorer
41Correct score [{score}]
45Correct score
46Halftime/fulltime correct score
47Halftime/fulltime
48{$competitor1} to win both halves
49{$competitor2} to win both halves
50{$competitor1} to win either half
51{$competitor2} to win either half
52Highest scoring half
53{$competitor1} highest scoring half
54{$competitor2} highest scoring half
551st/2nd half both teams to score
56{$competitor1} to score in both halves
57{$competitor2} to score in both halves
58Both halves over {total}
59Both halves under {total}
601st half - 1x2
621st half - {!goalnr} goal
631st half - double chance
641st half - draw no bet
651st half - handicap {hcp}
661st half - handicap
681st half - total
691st half - {$competitor1} total
701st half - {$competitor2} total
711st half - exact goals
741st half - odd/even
751st half - both teams to score
761st half - {$competitor1} clean sheet
771st half - {$competitor2} clean sheet
781st half - 1x2 & both teams to score
791st half - 1x2 & total
811st half - correct score
832nd half - 1x2
842nd half - {!goalnr} goal
852nd half - double chance
862nd half - draw no bet
872nd half - handicap {hcp}
882nd half - handicap
902nd half - total
912nd half - {$competitor1} total
922nd half - {$competitor2} total
932nd half - exact goals
942nd half - odd/even
952nd half - both teams to score
962nd half - {$competitor1} clean sheet
972nd half - {$competitor2} clean sheet
982nd half - correct score
100When will the {!goalnr} goal be scored (15 min interval)
101When will the {!goalnr} goal be scored (10 min interval)
10510 minutes - 1x2 from {from} to {to}
122Will there be a penalty shootout
136Booking 1x2
137{!bookingnr} booking
138Total booking points
139Total bookings
142Exact bookings
143{$competitor1} exact bookings
144{$competitor2} exact bookings
146Sending off
147{$competitor1} sending off
148{$competitor2} sending off
1491st half - booking 1x2
1501st half - {!bookingnr} booking
1511st half - total booking points
1521st half - total bookings
1531st half - {$competitor1} total bookings
1541st half - {$competitor2} total bookings
1551st half - exact bookings
1561st half - {$competitor1} exact bookings
1571st half - {$competitor2} exact bookings
1591st half - sending off
1601st half - {$competitor1} sending off
1611st half - {$competitor2} sending off
162Corner 1x2
163{!cornernr} corner
164Last corner
165Corner handicap
166Total corners
167{$competitor1} total corners
168{$competitor2} total corners
169Corner range
170{$competitor1} corner range
171{$competitor2} corner range
172Odd/even corners
1731st half - corner 1x2
1741st half - {!cornernr} corner
1751st half - last corner
1761st half - corner handicap
1771st half - total corners
1801st half - {$competitor1} exact corners
1811st half - {$competitor2} exact corners
1821st half - Corner range
1831st half - odd/even corners
184{!goalnr} goal & 1x2
199Correct score
220Will there be overtime
540Double chance (match) & 1st half both teams score
541Double chance (match) & 2nd half both teams score
5421st half - double chance & both teams to score
5432nd half - 1x2 & both teams to score
5442nd half - 1x2 & total
5452nd half - double chance & both teams to score
546Double chance & both teams to score
547Double chance & total
548Multigoals
549{$competitor1} multigoals
550{$competitor2} multigoals
551Multiscores
5521st half - multigoals
5532nd half - multigoals
770{player} assists (incl. overtime)
775{player} goals (incl. overtime)
776{player} shots (incl. overtime)
777{player} shots on goal (incl. overtime)
778{player} passes (incl. overtime)
780{player} tackles (incl. overtime)
818Halftime/fulltime & total
819Halftime/fulltime & 1st half total
820Halftime/fulltime & exact goals
854{$competitor1} or over {total}
855{$competitor1} or under {total}
856Draw or over {total}
857Draw or under {total}
858{$competitor2} or over {total}
859{$competitor2} or under {total}
860{$competitor1} or both teams to score
861Draw or both teams to score
862{$competitor2} or both teams to score
863{$competitor1} or any clean sheet
864Draw or any clean sheet
865{$competitor2} or any clean sheet
879{$competitor2} to win
880{$competitor1} to win
881Any team to win
882{player} to score (incl. overtime)
888Anytime goalscorer & 1x2
889Anytime goalscorer & correct score
890{!goalnr} goalscorer & correct score
891{!goalnr} goalscorer & 1x2
11791st Half Result or Match Result
1183{player} total shots (incl. overtime)
1185{player} total shots on goal (incl. overtime)
1187{player} total passes (incl. overtime)
1189{player} total tackles (incl. overtime)
1191{player} to be carded (incl. overtime)
tip

This widget requires an adapter to supply match, market and odds data. See the Adapter overview.

<script type="text/javascript">
// Widget loader script from Step 3 
ndatory 
// -------- Data + Transform functions --------

async function getDataMarket(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataMarket(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    market: {
      id: data.marketId,
      name: data.marketName,
      outcomes: data.outcomes.map((outcome) =>  { id: outcome.id, name: outcome.name, odds: outcome.odds } )
    },
    event: { id: data.eventId, type: data.eventType }
  };
  */

  return {
    market: {
      id: "sr:market:1",
      name: "Match Winner",
      outcomes: [
        { id: "1", name: "Home", odds: 2.5 },
        { id: "X", name: "Draw", odds: 3.0 },
        { id: "2", name: "Away", odds: 3.2 }
      ]
    },
    event: { id: "sr:match:12345", type: "uf" }
  };
}

async function getDataAvailableMarketsForEvent(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataAvailableMarketsForEvent(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    selection: data.map((selection) => { type: selection.type, event: selection.event, market: selection.market})
  }; 
  */

  return {
    selection: [
      {
        type: "uf",
        event: "61513908",
        market: "1",
      },
    ],
  };
}

async function getDataEventMarkets(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataEventMarkets(data, args) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  let markets = []

  function mapMarkets(market){
    return {
      id: market.id,
      status: market.status,
      name: market.name,
      outcomes: market.outcomes.map((outcome) => {id: outcome.id, name: outcome.name , odds: { type: outcome.odds.type, value: outcome.odds.value}, status: outcome.status})
    };
  }

  return data.forEach(mapMarkets);
  */

  return {
    event: args.selection.event,
    markets: [
      {
        id: "1",
        status: "active",
        name: "1x2",
        outcomes: [
          {
            id: "1",
            name: "Tenhaisen",
            odds: { type: "eu", value: "1.88" },
            status: "active",
          },
          {
            id: "2",
            name: "draw",
            odds: { type: "eu", value: "3.85" },
            status: "active",
          },
          {
            id: "3",
            name: "Hoftenstain",
            odds: { type: "eu", value: "3.7" },
            status: "active",
          },
        ],
      },
    ],
  };
}

async function getDataEvent(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataEvent(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    event: {
      id: data.event,
      date: {
        displayValue: data.displayTime,
        startTime: data.dateTime,
      },
      sport: {
        id: data.sport.id,
        name: data.sport.name,
      },
      category: {
        id: data.category.id,
        name: data.category.country,
      },
      tournament: {
        id: data.tournament.id,
        name: data.tournament.name,
      },
      teams: data.teams.map((team) => {id: team.id, name: team.name}),
      isLive: data.isLive,
    },
  }; 
  
  */
  return {
    event: {
      id: args.selection.event,
      date: {
        displayValue: "14/01/26, 19:30",
        startTime: "2026-01-14T19:30:00.000Z",
      },
      sport: {
        id: "1",
        name: "Soccer",
      },
      category: {
        id: "30",
        name: "Germany",
      },
      tournament: {
        id: "42",
        name: "Liga Supreme",
      },
      teams: [
        { id: "1270229", name: "Tenhaisen" },
        { id: "31531", name: "Hoftenstain" },
      ],
      isLive: false,
    },
  };
}

async function getDataFilterMarkets(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataFilterMarkets(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    selection: data.selection.map((market) => {type: market.type, event: market.event, market: market.id})
  }
  */
  return {
    selection: [
      {
        type: "uf",
        event: "61513908",
        market: "1",
      },
    ],
  };
}

async function getDataBetSlipSelection(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataBetSlipSelection(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    selection: data.selection.map((market) => {event: market.event, market: market.market, outcome: market.outcome, type: market.type}),
  };
  */

  return {
    selection: [
      {
        event: "61513908",
        market: "1",
        outcome: "1",
        type: "uf",
      },
    ],
  };
}

async function getDataCashBackSelections(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataCashBackSelections(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    events: data.events.map((event) => {event: event.id, type: event.type}),
  }
  */

  return {
    events: [
      {
        event: "56418457",
        type: "uf",
      },
    ],
  };
}

async function getDataTickets(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataTickets(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    tickets: data.tickets.map((ticket) => {
      ticketId: ticket.id,
      bets: ticket.bets.map((bet) => {
        betId: bet.id,
        selections: bet.selections.map((selection) => {
          type: selection.type,
          event: selection.event,
          market: selection.market,
          outcome: selection.outcome,
          odds: { type: selection.odds.type, value: selection.odds.value },
        }),
        stake: bet.stake.map((stake) => {
          type: stake.type,
          currency: stake.currency,
          amount: stake.amount,
          mode: stake.mode
        }),
      }),
    }),
  };
  */

  return {
    tickets: [
      {
        ticketId: "ticket_123456",
        bets: [
          {
            betId: "bet_001",
            selections: [
              {
                type: "uf",
                event: "sr:match:12345",
                market: "1",
                outcome: "1",
                odds: { type: "eu", value: "2.10" }
              }
            ],
            stake: [{
              type: "cash",
              currency: "USD",
              amount: "10.00",
              mode: "total"
            }]
          }
        ],
        type: "ticket",
        version: "1.0"
      }
    ]
  };
}

async function getDataMatchEventSuggestedSelection(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataMatchEventSuggestedSelection(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    selections: data.selections.map((selection) => {
      event: selection.event,
      market: selection.market,
      outcome: selection.outcome,
      type: selection.type,
      specifiers: selection.specifiers
    })
  };
  */

  return {
    selections: [
      { event: "sr:match:12345", market: "1", outcome: "1", type: "uf" },
      { event: "sr:match:12345", market: "18", specifiers: "total=2.5", type: "uf" }
    ]
  };
}

async function getDataRecommendedSelections(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataRecommendedSelections(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    selection: data.selections.map((selection) => {
      event: selection.event,
      market: selection.market,
      outcome: selection.outcome,
      type: selection.type,
      specifiers: selection.specifiers
    })
  };
  */

  return {
    selection: [
      { event: "sr:match:12345", market: "1", outcome: "1", type: "uf" },
      { event: "sr:match:67890", market: "18", specifiers: "total=2.5", outcome: "13", type: "uf" }
    ]
  };
}

async function getDataCalculateCustomBetXML(args) {
  // Here fetch data from your data source and return it
  return {};
}

function transformDataCalculateCustomBetXML(data) {
  // Here transform your data into data structure exemplified by the object below.
  /*
  // Illustration how data transformation might work from client data to Adapter types
  return {
    payload: '<xml>data</xml>',
  }
  */
  return {
    payload: `<filtered_calculation_response generated_at="2025-04-16T13:29:08+00:00">
                <calculation odds="27.50303106727931" probability="0.027418715351118873" harmonization="false"/>
                <available_selections>
                  <event id="sr:match:12345678">
                    <markets>
                      <market id="65" specifiers="hcp=0:2" conflict="false">
                        <outcome id="1711" conflict="true"/>
                        <outcome id="1712" conflict="true"/>
                        <outcome id="1713" conflict="false"/>
                      </market>
                      ...
                    </markets>
                  </event>
                </available_selections>
              </filtered_calculation_response>`
  };
}

// -------- Adapter --------

const adapter = {
  config: {},
  endpoints: {
    market: (args, callback) => {
      getDataMarket(args)
        .then(data => transformDataMarket(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    availableMarketsForEvent: (args, callback) => {
      getDataAvailableMarketsForEvent(args)
        .then(data => transformDataAvailableMarketsForEvent(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    eventMarkets: (args, callback) => {
      getDataEventMarkets(args)
        .then(data => transformDataEventMarkets(data, args))
        .then(result => callback(undefined, result));
      return () => {};
    },

    event: (args, callback) => {
      getDataEvent(args)
        .then(data => transformDataEvent(data, args))
        .then(result => callback(undefined, result));
      return () => {};
    },

    filterMarkets: (args, callback) => {
      getDataFilterMarkets(args)
        .then(data => transformDataFilterMarkets(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    betSlipSelection: (args, callback) => {
      getDataBetSlipSelection(args)
        .then(data => transformDataBetSlipSelection(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    cashBackSelections: (args, callback) => {
      getDataCashBackSelections(args)
        .then(data => transformDataCashBackSelections(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    tickets: (args, callback) => {
      getDataTickets(args)
        .then(data => transformDataTickets(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    matchEventSuggestedSelection: (args, callback) => {
      getDataMatchEventSuggestedSelection(args)
        .then(data => transformDataMatchEventSuggestedSelection(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    recommendedSelections: (args, callback) => {
      getDataRecommendedSelections(args)
        .then(data => transformDataRecommendedSelections(data))
        .then(result => callback(undefined, result));
      return () => {};
    },

    calculateCustomBetXML: (args, callback) => {
      getDataCalculateCustomBetXML(args)
        .then(data => transformDataCalculateCustomBetXML(data))
        .then(result => callback(undefined, result));
      return () => {};
    },
  },
};
</script>
integration
:
"inline"
}
Default Card

Standard card with market name and outcome information.

javascript
{ cardVariant: "default" }
cardsLayout
:
"vertical"
}
{ integration: "button", modalPosition: "left" }
{ disableWidgetHeader: true }
PropertyTypeRequiredDefaultDescription
matchIdnumberYes-Sportradar match identifier. See Getting Identifiers
integrationstringNo"inline"Widget integration mode.
  • "inline": Always-visible display embedded directly in page content
  • "button": Compact button triggering modal with insights
cardsLayoutstringNo"vertical"Layout direction for insight cards.
  • "vertical": Cards stacked vertically, suitable for narrow containers
  • "horizontal": Cards arranged horizontally in rows, suitable for wide containers
cardVariantstringNo"default"Card display style variant.
  • "default": Standard card with market name and outcome information
  • "defaultIcon": Standard card with icon display
  • "compact": Condensed layout with minimal information
  • "button": Call-to-action button style with prominent outcome selection
  • "buttonFooter": Button style with footer emphasis
  • "single": Simplified single-insight display
outcomeOrderstringNo"bottom"Position of outcome name relative to odds. Options: "top", "bottom". Only applicable with cardVariant='compact', cardVariant='button', and cardVariant='buttonFooter'
outcomeButtonPositionstringNo"bottom"Position of call-to-action button in card. Options: "top", "bottom". Not applicable with cardVariant='button' and cardVariant='buttonFooter'
widgetTitlestring|falseNo"Bet Insights"Title displayed in widget header or button label. Set to false to hide title
widgetIconstring|falseNoDefault iconURL of custom icon image for button/header. Set to false to hide icon
disableWidgetHeaderbooleanNofalseWhen true, hides widget header in inline integration mode
enableCollapsebooleanNotrueWhen true, allows collapsing widget content in inline mode. Only applicable when disableWidgetHeader=false
startCollapsedbooleanNofalseWhen true and enableCollapse is true, widget starts in collapsed state
modalPositionstringNo"left"Direction modal opens in button integration. Options: "left", "right", "bottom". Automatically adjusts for RTL languages
modalMaxHeightnumber|stringNo-Maximum height of modal content. Allowed units: %, px, vh. When number (without units) is used, defaults to px. Only applicable with integration='button' and cardsLayout='vertical'
isMobilebooleanNofalseWhen set to true, opens pop-up on bottom edge of the viewport, stretched from left to right (only applicable with integration='button')
minOddsnumberNo-Minimum odds value filter. Only displays insights with odds greater than or equal to this value
maxOddsnumberNo-Maximum odds value filter. Only displays insights with odds less than or equal to this value
ignoreAdapterValuesbooleanNofalseWhen true, uses Sportradar's standard market/outcome names instead of adapter-provided translations
capitalizeMarketNameAndOutputbooleanNofalseWhen true, capitalizes first letter of market names and outcome names
onItemClickfunctionNo-Callback triggered when insight card or outcome is clicked. Receives event and outcome data for bet slip integration
data-sr-card-variant
  • Example: filters.sport.hidden → Complex objects must be passed as JSON strings
  • info

    In HTML integration, the properties go into the parent HTML object as object properties, prefixed with data-sr- as explained above.

    Only base property support

    This method supports only simple (base) properties and does not support properties that require functions.

    info

    In all examples replace sportradar in the widgetloader URL path with your clientId.

    Example if your clientId is client1:

    • This URL: https://widgets.sir.sportradar.com/sportradar/widgetloader
    • becomes: https://widgets.sir.sportradar.com/client1/widgetloader
    html
    <script>
        (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',
            adapterDataSource: '<DATA_SOURCE>'
        });
    
        const widgetProps = {
            // Required base props
            matchId: 61591316,
            // ... additional required props
    
            // Setup-specific: Props that produce the configuration shown in #Main Configurable Features section
            integration: "button",
            modalPosition: "right"
    
            // ... Optional: Additional customization props (see API Reference)
        };
    
        SIR('addWidget', '#bet-insights', 'betInsights', widgetProps);
    </script>
    
    <div id="bet-insights"></div>
    javascript
        let betSlipChangeCallback = undefined;
        let betSlipState = { // Simplified bet slip integration for demonstration purposes
            selection: [],
        };
    
        // Called on user interactions
        function onItemClick(target, data) {
            if (target === 'externalOutcome') { //  When user clicks an outcome on an insight card
                // Construct a selection object in the shape your bet slip integration expects.
                // The widget provides external IDs via data.externalEvent, data.externalMarket,
                // and data.externalOutcome — map them to your own format below.
                const bet = {
                    type: 'uf',
                    event: `${data.externalEvent.id}`,
                    market: `${data.externalMarket.id}`,
                    outcome: `${data.externalOutcome.id}`
                };
                if (data.externalMarket.specifier && data.externalMarket.specifier.value) {
                    bet.specifiers = `${data.externalMarket.specifier.value}`;
                }
    
                // Add the new selection
                betSlipState = {
                    selection: [...betSlipState.selection, bet]
                };
    
                // Notify the adapter so the widget reflects the updated bet slip state
                betSlipChangeCallback && betSlipChangeCallback(betSlipState);
            }
        }
    
        const widgetProps = {
            matchId: 61591316,
            onItemClick: onItemClick,
            // ... Additional customization props (see API Reference)
        };
    
        SIR('addWidget', '#bet-insights', 'betInsights', widgetProps);
    odds
    ,
    source: 'bet_insights'
    });
    }
    }
    javascript
        const adapter = {
            endpoints: {
                betSlipSelection: (args, callback) => {
                    yourBetSlipStore.onBetSlipChange((currentSelections) => {
                        callback(undefined, {
                            selection: currentSelections.map((sel) => ({
                                type: 'uf',
                                event: sel.eventId,         // e.g., "sr:match:12345"
                                market: sel.marketId,       // e.g., "38"
                                specifiers: sel.specifiers, // e.g., "goalNr=1" (optional)
                                outcome: sel.outcomeId,     // e.g., "sr:player:1050245"
                                odds: { type: 'eu', value: sel.odds },
                            })),
                        });
                    });
    
                    return () => {
                        yourBetSlipStore.offBetSlipChange();
                    };
                },
            },
        };
    
        // USE ONLY ONE APPROPRIATE TO YOUR ADAPTER TYPE
        SIR('registerAdapter', adapter); // Generic-Sportradar & Self-hosted
        SIR('registerAdapter', '<HOSTED_ADAPTER_NAME>', adapter); // Custom-mapping
    
        function onItemClick(args) {
            if (args.type === 'addSelectionsToBetSlip') {
                // Check if markets are still open and available
                const isValid = checkSelectionValid(args.data.selections);
    
                if (isValid) {
                    yourBetSlipStore.addSelections(
                        convertToStoreSelection(args.data.selections)
                    );
    
                    // Check if odds are the same as they were shown in widget
                    const haveOddsChanged = checkOdds(args.data.selections);
    
                    // Visual feedback
                    if (haveOddsChanged) {
                        showToast(`Odds have changed!, ${outcomeData.outcome.name} was added with adjusted odds.`);
                    } else {
                        showToast(`${outcomeData.outcome.name} added to bet slip`);
                    }
    
                } else {
                    // Visual feedback
                    showToast(`Selection is no longer available.`);
                }
            }
        }
    
        const widgetProps = {
            matchId: 61591316,
            onItemClick: onItemClick,
            // ... Additional customization props (see API Reference)
        };
    
        SIR('addWidget', '#bet-insights', 'betInsights', widgetProps);
    script
    >
    (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',
    adapterDataSource: '<DATA_SOURCE>'
    });
    // Tracks the current bet slip selections in the format the widget expects.
    // In production, replace this with your own bet slip store / state manager.
    let betSlipState = { selection: [] };
    let notifyWidget = null;
    const adapter = {
    endpoints: {
    betSlipSelection: (args, callback) => {
    // Store the callback so we can notify the widget whenever selections change.
    notifyWidget = callback;
    // Push the current state immediately so the widget is in sync on load.
    callback(undefined, betSlipState);
    return () => {
    notifyWidget = null;
    };
    },
    },
    };
    // Register the adapter once on page load — USE ONLY ONE APPROPRIATE TO YOUR ADAPTER TYPE
    SIR('registerAdapter', adapter); // Generic-Sportradar & Self-hosted
    // SIR('registerAdapter', '<HOSTED_ADAPTER_NAME>', adapter); // Custom-mapping
    function onItemClick(args) {
    if (args.type === 'addSelectionsToBetSlip') {
    // Map the widget's selections to the format the betSlipSelection endpoint expects.
    const newSelections = args.data.selections.map((sel) => ({
    type: 'uf',
    event: sel.eventId, // e.g., "sr:match:12345"
    market: sel.marketId, // e.g., "38"
    specifiers: sel.specifiers, // e.g., "goalNr=1" (optional)
    outcome: sel.outcomeId, // e.g., "sr:player:1050245"
    odds: { type: 'eu', value: sel.odds },
    }));
    betSlipState = {
    selection: [...betSlipState.selection, ...newSelections],
    };
    // Notify the widget so it immediately highlights the newly selected outcomes.
    notifyWidget && notifyWidget(undefined, betSlipState);
    }
    }
    const widgetProps = {
    matchId: 61591316,
    onItemClick: onItemClick,
    // ... Additional customization props (see API Reference)
    };
    SIR('addWidget', '#bet-insights', 'betInsights', widgetProps);
    </script>
    <div id="bet-insights"></div>
    danger

    The testMarkets prop is intended for development and testing only — do not use it in production environments. It is only needed for self-hosted adapter implementations.

    <script>
        (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'
        });
    
        const adapter = {
            endpoints: {
              event: (args, callback) => {
                console.log(args); // development-only: logs incoming request args so developers can inspect payloads (remove in production)
    
                callback(undefined, {
                  event: {
                    id: args.selection.event,
                    isLive: false,
                    date: {
                      displayValue: "2026-02-02 18:00",
                      startTime: "2026-02-02T18:00:00Z",
                    },
                    sport: { id: "sr:sport:1", name: "Soccer" },
                    category: { id: "sr:category:1", name: "England" },
                    tournament: { id: "sr:tournament:17", name: "Premier League" },
                    teams: [
                      { id: "sr:competitor:1", name: "Manchester United" },
                      { id: "sr:competitor:2", name: "Liverpool" },
                    ],
                  },
                });
                return () => {};
              },
              market: (args, callback) => {
                console.log(args); // development-only: logs incoming request args so developers can inspect payloads (remove in production)
    
                callback(undefined, {
                  event,
                  markets: [
                    {
                      id: "1",
                      name: "1x2",
                      status: "active",
                      outcomes: [
                        {
                          id: "1",
                          name: "Home",
                          status: "active",
                          odds: { type: 'eu', value: "1.85" },
                        },
                        {
                          id: "X",
                          name: "Draw",
                          status: "active",
                          odds: { type: 'eu', value: "3.40" },
                        },
                        {
                          id: "2",
                          name: "Away",
                          status: "active",
                          odds: { type: 'eu', value: "4.20" },
                        },
                      ],
                    },
                    {
                      id: "18",
                      name: "Total Goals",
                      specifiers: "total=1.5",
                      status: "active",
                      outcomes: [
                        {
                          id: "12",
                          name: "Over 2.5",
                          status: "active",
                          odds: { type: 'eu', value: "1.95" },
                        },
                        {
                          id: "13",
                          name: "Under 2.5",
                          status: "active",
                          odds: { type: 'eu', value: "1.85" },
                        },
                      ],
                    },
                  ],
                });
                return () => {};
              },
            },
          };
    
        SIR('registerAdapter', adapter);
    
        SIR('addWidget', '#bet-insights', 'betInsights', {
            matchId: 61591316,
            testMarkets: "1|18;total=0.5,total=1.5|8"
        });
    </script>
    
    <div id="bet-insights"></div>
    :
    "Draw"
    ,
    status
    :
    "active"
    ,
    odds
    :
    {
    type
    :
    "eu"
    ,
    value
    :
    "3.40"
    }
    },
    { id: "2", name: "Away", status: "active", odds: { type: "eu", value: "3.50" } }
    ],
    status: "suspended"
    };
    ClassCustomization options
    srct-ins-buttonbackground-color, border-radius
    srct-ins-button__iconcolor
    srct-ins-button_textcolor, font-size, font-family
    srct-ins-headerbackground-color, border-color, border-width
    srct-ins-header__iconcolor
    srct-ins-header__textcolor, font-size, font-weight
    srct-ins-header__arrowcolor
    srct-ins-containerbackground-color, font-family
    srct-ins-cardbackground-color, border-radius, border-color, box-shadow
    srct-ins-textcolor, text-decoration
    srct-ins-showmorecolor, font-size
    srct-ins-popupbackground-color, color, font-size, font-family
    srct-ins-popup__iconcolor
    srct-ins-ctabackground-color, border-color
    srct-ins-marketcolor, font-size, font-weight
    srct-ins-market__iconcolor
    srct-ins-outcomebackground-color, border-radius, padding
    srct-ins-outcome--selectedbackground-color
    srct-ins-outcome--disabledbackground-color
    srct-ins-outcome__namecolor, font-size, font-weight
    srct-ins-outcome__valuecolor, font-size, font-weight
    srct-ins-bubble-countbackground-color, border-color, color
    srct-ins-bubble-count-textcolor
    srct-ins-bubble-newbackground-color, border-color, color
    srct-ins-robot-iconcolor
    integration
    :
    'inline'
    ,
    }
    #4. Mobile-First Layouts

    In a mobile view, screen real estate is at a premium. The inline mode stacks naturally between other page elements, ensuring that the insights don't overlap or hide important UI components like the navigation bar or the "Place Bet" button.

    javascript
    {
        integration: 'button',
        isMobile: true,
        modalPosition: 'bottom'
    }
    Bet Insights Mobile

    Integration Best Practices

    Performance optimization, security considerations, and deployment strategies.

    Learn More