---
title: "Tournament Bet Insights"
canonical_url: "https://apidocs.sportradar.com/resources/widgets/docs/bet-insights/tournament-bet-insights"
markdown_url: "https://apidocs.sportradar.com/resources/widgets/docs/bet-insights/tournament-bet-insights.md"
last_updated: "2026-08-11T13:11:13Z"
---

# Tournament Bet Insights

**Tournament Bet Insights** widget displays AI-powered betting insights and recommendations across multiple matches within a tournament, league, or season. The widget analyzes betting patterns, historical data, and statistical trends to highlight valuable betting opportunities from upcoming matches in the specified competition. It aggregates insights from multiple events to help users identify the most promising betting opportunities across an entire tournament or league, making it ideal for overview pages, tournament hubs, and league standings sections. The widget offers the same flexible integration modes and card layout variants as the match-level [Bet Insights](https://apidocs.sportradar.com/resources/widgets/docs/bet-insights/bet-insights.md) widget, including inline display for dedicated sections or button-triggered modal for compact placements.

> **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.
>
> <!-- TODO: Add link to UOF docs... -->

> **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.

**Web Inline**

![Tournament Bet Insights Web Inline](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-tournament.png){width=100%}

See the [Tournament Bet Insights widget demo](https://widgets.sir.sportradar.com/bet-insights#matches:\(matchId:63684665,sportId:1,uniqueTournamentId:136\),sidebar:\(selected:betInsightsVersions\),widget:\(name:betInsights.tournament\)).

**Web Pop-up**

![Tournament Bet Insights Web Pop-up](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-button-bottom-placement.png){width=400}

See the [Tournament Bet Insights widget demo](https://widgets.sir.sportradar.com/bet-insights#matches:\(matchId:63684665,sportId:1,uniqueTournamentId:136\),sidebar:\(selected:betInsightsVersions\),widget:\(name:betInsights.tournament\)).

**Mobile Inline**

![Tournament Bet Insights Mobile Inline](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-tournament-mobile.png){width=300}

See the [Tournament Bet Insights widget demo](https://widgets.sir.sportradar.com/bet-insights#matches:\(matchId:63684665,sportId:1,uniqueTournamentId:136\),sidebar:\(selected:betInsightsVersions\),widget:\(name:betInsights.tournament\)).

**Mobile Pop-up**

![Tournament Bet Insights Mobile Pop-up Button](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-button-mobile.png){width=260}

![Tournament Bet Insights Mobile Pop-up Open](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-button-mobile-open.png){width=260}

See the [Tournament Bet Insights widget demo](https://widgets.sir.sportradar.com/bet-insights#matches:\(matchId:63684665,sportId:1,uniqueTournamentId:136\),sidebar:\(selected:betInsightsVersions\),widget:\(name:betInsights.tournament\)).

## Supported Content and Environment

### Required Parameters

- **widget-name**: `betInsights.tournament`

**Required identifiers (at least one):**

At least one of the following identifiers must be provided:

- **matchId**: Sportradar match identifier. Shows insights for matches related to this match's tournament
- **tournamentId**: Sportradar tournament identifier. Shows insights for all matches in this specific tournament
- **uniqueTournamentId**: Sportradar unique tournament identifier. Shows insights for matches across all seasons of this tournament
- **seasonId**: Sportradar season identifier. Shows insights for all matches in this specific season

See [Getting Identifiers](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/getting-identifiers.md) for details on obtaining these identifiers.

**Environment Requirements**

## Technical Requirements:

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

**Supported Sports**

- Soccer

**Supported Languages**

| Code    | Language              | Native              |
| ------- | --------------------- | ------------------- |
| `sqi`   | Albanian              | Shqip               |
| `aa`    | Arabic                | العربية             |
| `hye`   | Armenian              | Հայերեն             |
| `aze`   | Azerbaijani           | Azərbaycan dili     |
| `bs`    | Bosnian               | Bosanski            |
| `bg`    | Bulgarian             | Български           |
| `zh`    | Chinese (Simplified)  | 简体中文                |
| `zht`   | Chinese (Traditional) | 中文繁體                |
| `hr`    | Croatian              | Hrvatski            |
| `cs`    | Czech                 | Česky               |
| `da`    | Danish                | Dansk               |
| `nl`    | Dutch                 | Nederlands          |
| `en`    | English               |                     |
| `en_us` | English (US)          |                     |
| `et`    | Estonian              | Eesti               |
| `fi`    | Finnish               | Suomeksi            |
| `fr`    | French                | Français            |
| `ka`    | Georgian              | ქართული             |
| `de`    | German                | Deutsch             |
| `el`    | Greek                 | Eλληνικά            |
| `heb`   | Hebrew                | עברית               |
| `hi`    | Hindi                 | हिन्दी              |
| `hu`    | Hungarian             | Magyar              |
| `isl`   | Icelandic             | Íslenska            |
| `id`    | Indonesian            | Bahasa Indonesia    |
| `it`    | Italian               | Italiano            |
| `ja`    | Japanese              | 日本語                 |
| `km`    | Khmer                 | ខ្មែរ               |
| `ko`    | Korean                | 한국어                 |
| `lv`    | Latvian               | Latviešu            |
| `lt`    | Lithuanian            | Lietuvių            |
| `mk`    | Macedonian            | Македонски          |
| `me`    | Montenegrin           | Crnogorski          |
| `no`    | Norwegian             | Norsk               |
| `pl`    | Polish                | Polski              |
| `pt`    | Portuguese            | Português           |
| `br`    | Portuguese (Brazil)   | Português do Brasil |
| `ro`    | Romanian              | Română              |
| `ru`    | Russian               | Русский             |
| `sr`    | Serbian (Cyrillic)    | Cрпски              |
| `srl`   | Serbian (Latin)       | Srpski              |
| `sk`    | Slovak                | Slovenčina          |
| `sl`    | Slovenian             | Slovenščina         |
| `es`    | Spanish               | Español             |
| `sw`    | Swahili               | Kiswahili           |
| `se`    | Swedish               | Svenska             |
| `th`    | Thai                  | ไทย                 |
| `tr`    | Turkish               | Türkçe              |
| `tuk`   | Turkmen               |                     |
| `tl`    | Filipino (Tagalog)    | Filipino            |
| `ukr`   | Ukrainian             | Українська          |
| `vi`    | Vietnamese            | Tiếng Việt          |

**Supported Markets**

##### Soccer

| Market ID | Market Name                                              |
| --------- | -------------------------------------------------------- |
| 1         | 1x2                                                      |
| 8         | {!goalnr} goal                                           |
| 9         | Last goal                                                |
| 10        | Double chance                                            |
| 11        | Draw no bet                                              |
| 12        | {$competitor1} no bet                                    |
| 13        | {$competitor2} no bet                                    |
| 14        | Handicap {hcp}                                           |
| 15        | Winning margin                                           |
| 16        | Handicap                                                 |
| 18        | Total                                                    |
| 19        | {$competitor1} total                                     |
| 20        | {$competitor2} total                                     |
| 21        | Exact goals                                              |
| 23        | {$competitor1} exact goals                               |
| 24        | {$competitor1} exact goals                               |
| 24        | {$competitor2} exact goals                               |
| 25        | Goal range                                               |
| 26        | Odd/even                                                 |
| 27        | {$competitor1} odd/even                                  |
| 28        | {$competitor2} odd/even                                  |
| 29        | Both teams to score                                      |
| 30        | Which team to score                                      |
| 31        | {$competitor1} clean sheet                               |
| 32        | {$competitor2} clean sheet                               |
| 33        | {$competitor1} win to nil                                |
| 34        | {$competitor2} win to nil                                |
| 35        | 1x2 & both teams to score                                |
| 36        | Total & both teams to score                              |
| 37        | 1x2 & total                                              |
| 38        | {!goalnr} goalscorer                                     |
| 39        | Last goalscorer                                          |
| 40        | Anytime goalscorer                                       |
| 41        | Correct score [{score}]                                  |
| 45        | Correct score                                            |
| 46        | Halftime/fulltime correct score                          |
| 47        | Halftime/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                        |
| 52        | Highest scoring half                                     |
| 53        | {$competitor1} highest scoring half                      |
| 54        | {$competitor2} highest scoring half                      |
| 55        | 1st/2nd half both teams to score                         |
| 56        | {$competitor1} to score in both halves                   |
| 57        | {$competitor2} to score in both halves                   |
| 58        | Both halves over {total}                                 |
| 59        | Both halves under {total}                                |
| 60        | 1st half - 1x2                                           |
| 62        | 1st half - {!goalnr} goal                                |
| 63        | 1st half - double chance                                 |
| 64        | 1st half - draw no bet                                   |
| 65        | 1st half - handicap {hcp}                                |
| 66        | 1st half - handicap                                      |
| 68        | 1st half - total                                         |
| 69        | 1st half - {$competitor1} total                          |
| 70        | 1st half - {$competitor2} total                          |
| 71        | 1st half - exact goals                                   |
| 74        | 1st half - odd/even                                      |
| 75        | 1st half - both teams to score                           |
| 76        | 1st half - {$competitor1} clean sheet                    |
| 77        | 1st half - {$competitor2} clean sheet                    |
| 78        | 1st half - 1x2 & both teams to score                     |
| 79        | 1st half - 1x2 & total                                   |
| 81        | 1st half - correct score                                 |
| 83        | 2nd half - 1x2                                           |
| 84        | 2nd half - {!goalnr} goal                                |
| 85        | 2nd half - double chance                                 |
| 86        | 2nd half - draw no bet                                   |
| 87        | 2nd half - handicap {hcp}                                |
| 88        | 2nd half - handicap                                      |
| 90        | 2nd half - total                                         |
| 91        | 2nd half - {$competitor1} total                          |
| 92        | 2nd half - {$competitor2} total                          |
| 93        | 2nd half - exact goals                                   |
| 94        | 2nd half - odd/even                                      |
| 95        | 2nd half - both teams to score                           |
| 96        | 2nd half - {$competitor1} clean sheet                    |
| 97        | 2nd half - {$competitor2} clean sheet                    |
| 98        | 2nd half - correct score                                 |
| 100       | When will the {!goalnr} goal be scored (15 min interval) |
| 101       | When will the {!goalnr} goal be scored (10 min interval) |
| 105       | 10 minutes - 1x2 from {from} to {to}                     |
| 122       | Will there be a penalty shootout                         |
| 136       | Booking 1x2                                              |
| 137       | {!bookingnr} booking                                     |
| 138       | Total booking points                                     |
| 139       | Total bookings                                           |
| 142       | Exact bookings                                           |
| 143       | {$competitor1} exact bookings                            |
| 144       | {$competitor2} exact bookings                            |
| 146       | Sending off                                              |
| 147       | {$competitor1} sending off                               |
| 148       | {$competitor2} sending off                               |
| 149       | 1st half - booking 1x2                                   |
| 150       | 1st half - {!bookingnr} booking                          |
| 151       | 1st half - total booking points                          |
| 152       | 1st half - total bookings                                |
| 153       | 1st half - {$competitor1} total bookings                 |
| 154       | 1st half - {$competitor2} total bookings                 |
| 155       | 1st half - exact bookings                                |
| 156       | 1st half - {$competitor1} exact bookings                 |
| 157       | 1st half - {$competitor2} exact bookings                 |
| 159       | 1st half - sending off                                   |
| 160       | 1st half - {$competitor1} sending off                    |
| 161       | 1st half - {$competitor2} sending off                    |
| 162       | Corner 1x2                                               |
| 163       | {!cornernr} corner                                       |
| 164       | Last corner                                              |
| 165       | Corner handicap                                          |
| 166       | Total corners                                            |
| 167       | {$competitor1} total corners                             |
| 168       | {$competitor2} total corners                             |
| 169       | Corner range                                             |
| 170       | {$competitor1} corner range                              |
| 171       | {$competitor2} corner range                              |
| 172       | Odd/even corners                                         |
| 173       | 1st half - corner 1x2                                    |
| 174       | 1st half - {!cornernr} corner                            |
| 175       | 1st half - last corner                                   |
| 176       | 1st half - corner handicap                               |
| 177       | 1st half - total corners                                 |
| 180       | 1st half - {$competitor1} exact corners                  |
| 181       | 1st half - {$competitor2} exact corners                  |
| 182       | 1st half - Corner range                                  |
| 183       | 1st half - odd/even corners                              |
| 184       | {!goalnr} goal & 1x2                                     |
| 199       | Correct score                                            |
| 220       | Will there be overtime                                   |
| 540       | Double chance (match) & 1st half both teams score        |
| 541       | Double chance (match) & 2nd half both teams score        |
| 542       | 1st half - double chance & both teams to score           |
| 543       | 2nd half - 1x2 & both teams to score                     |
| 544       | 2nd half - 1x2 & total                                   |
| 545       | 2nd half - double chance & both teams to score           |
| 546       | Double chance & both teams to score                      |
| 547       | Double chance & total                                    |
| 548       | Multigoals                                               |
| 549       | {$competitor1} multigoals                                |
| 550       | {$competitor2} multigoals                                |
| 551       | Multiscores                                              |
| 552       | 1st half - multigoals                                    |
| 553       | 2nd 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)                        |
| 818       | Halftime/fulltime & total                                |
| 819       | Halftime/fulltime & 1st half total                       |
| 820       | Halftime/fulltime & exact goals                          |
| 854       | {$competitor1} or over {total}                           |
| 855       | {$competitor1} or under {total}                          |
| 856       | Draw or over {total}                                     |
| 857       | Draw or under {total}                                    |
| 858       | {$competitor2} or over {total}                           |
| 859       | {$competitor2} or under {total}                          |
| 860       | {$competitor1} or both teams to score                    |
| 861       | Draw or both teams to score                              |
| 862       | {$competitor2} or both teams to score                    |
| 863       | {$competitor1} or any clean sheet                        |
| 864       | Draw or any clean sheet                                  |
| 865       | {$competitor2} or any clean sheet                        |
| 879       | {$competitor2} to win                                    |
| 880       | {$competitor1} to win                                    |
| 881       | Any team to win                                          |
| 882       | {player} to score (incl. overtime)                       |
| 888       | Anytime goalscorer & 1x2                                 |
| 889       | Anytime goalscorer & correct score                       |
| 890       | {!goalnr} goalscorer & correct score                     |
| 891       | {!goalnr} goalscorer & 1x2                               |
| 1179      | 1st 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)                   |

## Adapter

> **Tip**
>
> This widget requires an adapter to supply match, market and odds data. See the [Adapter overview](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Overview.md).

For implementation guidance, example adapter implementations, and the adapter endpoint contract required by this widget, see the [Adapter overview](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Overview.md). 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.

### 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
<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: "sr:match:12345"
  };
}

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: "decimal", value: "1.88" },
            status: "active",
          },
          {
            id: "2",
            name: "draw",
            odds: { type: "decimal", value: "3.85" },
            status: "active",
          },
          {
            id: "3",
            name: "Hoftenstain",
            odds: { type: "decimal", 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,
          eventId: selection.eventId,
          marketId: selection.marketId,
          outcomeId: selection.outcomeId,
          odds: { type: selection.odds.type, value: selection.odds.value },
        }),
        odds: { type: bet.odds.type, value: bet.odds.value },
        stake: bet.stake.map((stake) => {
          type: stake.type,
          currency: stake.currency,
          amount: stake.amount,
          mode: stake.mode
        }),
      }),
      version: ticket.version,
    }),
  };
  */

  return {
    tickets: [
      {
        ticketId: "ticket_123456",
        bets: [
          {
            betId: "bet_001",
            selections: [
              {
          type: "uf",
          productId: "1",
          eventId: "sr:match:12345",
          marketId: "1",
          outcomeId: "1",
          odds: { type: "decimal", value: "2.10" },
          context: {
            eventName: "Manchester United vs Liverpool",
            marketName: "1x2",
            outcomeName: "Home",
            tournament: "Premier League",
            sportName: "Soccer",
            categoryName: "England",
            isLive: false,
            eventStartTime: "1738519200000",
            sportId: "1"
          }
        }
            ],
            odds: { type: "decimal", value: "2.10" },
            stake: [{
              type: "cash",
              currency: "USD",
              amount: "10.00",
              mode: "total"
            }],
            payout: [{ type: "cash", currency: "USD", amount: "21.00" }]
          }
        ],
        version: "2.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>
```

## Main Configurable Features

### Integration Mode

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

**Inline**

![Inline Integration](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insight.png){width=100%}

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

```javascript
{ integration: "inline" }
```

**Button (Pop-up)**

![Button Pop-up Integration](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-button-bottom-placement.png){width=400}

Compact button that opens insights in a pop-up overlay — ideal for space-constrained layouts.

```javascript
{ integration: "button", modalPosition: "right" }
```

***

### Widget Title

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

**With Icon (Default)**

![Title with Icon](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-button-mobile.png){width=200}

```javascript
{ widgetTitle: "Bet Insights" }
```

**Without Icon**

![Title without Icon](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-inisghts-isMobile-without-icon.png){width=200}

```javascript
{ widgetIcon: false }
```

***

### Card Variants

Controls the visual style of each insight card.

**Default**

![Default Card](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-card-variant-default.png){width=100%}

Standard card with market name and outcome information.

```javascript
{ cardVariant: "default" }
```

**Default with Icon**

![Default with Icon](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-card-variant-default-with-icon.png){width=100%}

Standard card enhanced with an insight icon.

```javascript
{ cardVariant: "defaultIcon" }
```

**Compact**

![Compact Card](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-card-variant-compact.png){width=100%}

Condensed layout — fits more insights in less space.

```javascript
{ cardVariant: "compact" }
```

**Button**

![Button Card](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-card-variant-button.png){width=100%}

Prominent call-to-action style with bold outcome selection.

```javascript
{ cardVariant: "button" }
```

**Button Footer**

![Button Footer Card](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-card-variant-footer-button.png){width=100%}

Button-style card with emphasis on the footer area.

```javascript
{ cardVariant: "buttonFooter" }
```

***

### Outcome Position

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

| Position             | Config                            | Description                 |
| -------------------- | --------------------------------- | --------------------------- |
| **Bottom** (default) | `outcomeButtonPosition: "bottom"` | Outcome name below the odds |
| **Top**              | `outcomeButtonPosition: "top"`    | Outcome name above the odds |

***

### Cards Layout

**Vertical**

![Vertical Layout](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-popup-cards-layout-vertical.png){width=450}

Cards stacked vertically — suitable for narrow containers and sidebars.

```javascript
{ cardsLayout: "vertical" }
```

**Horizontal**

![Horizontal Layout](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-popup-cards-layout-horizontal.png){width=450}

Cards arranged in horizontal rows — suitable for wide containers.

```javascript
{ cardsLayout: "horizontal" }
```

***

### Pop-up Direction

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

**Left**

![Pop-up Left](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-popup-popup-direction-left.png){width=100%}

```javascript
{ integration: "button", modalPosition: "left" }
```

**Right**

![Pop-up Right](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-popup-popup-direction-right.png){width=100%}

```javascript
{ integration: "button", modalPosition: "right" }
```

**Bottom**

![Pop-up Bottom](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-popup-popup-direction-bottom.png){width=100%}

```javascript
{ integration: "button", modalPosition: "bottom" }
```

***

### Widget Header Options

**Header Disabled**

![Header Disabled](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-disableWidgetHeader.png){width=100%}

Hides the widget header entirely in inline mode.

```javascript
{ disableWidgetHeader: true }
```

**Collapse Enabled**

![Collapse Enabled](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-enableCollapse.png){width=100%}

Allows users to collapse and expand the widget content.

```javascript
{ enableCollapse: true }
```

**Start Collapsed**

![Start Collapsed](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/bet-insights-startCollapsed.png){width=100%}

Widget loads in a collapsed state. Requires `enableCollapse: true`.

```javascript
{ enableCollapse: true, startCollapsed: true }
```

See the [Bet Insights demo](https://widgets.sir.sportradar.com/bet-insights) for live examples.

## API Reference

### Tournament-Specific Parameters

| Property             | Type     | Required        | Default | Description                                                                                                                                                                                                                                                                                                 |
| -------------------- | -------- | --------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `matchId`            | `number` | **Conditional** | -       | Sportradar match identifier. Shows insights for matches in the same tournament as this match. At least one of `matchId`, `tournamentId`, `uniqueTournamentId`, or `seasonId` is required. See [Getting Identifiers](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/getting-identifiers.md) |
| `tournamentId`       | `number` | **Conditional** | -       | Sportradar tournament identifier. Shows insights from all matches in this specific tournament. At least one of the four ID props is required                                                                                                                                                                |
| `uniqueTournamentId` | `number` | **Conditional** | -       | Sportradar unique tournament identifier. Shows insights from matches across all seasons of this tournament. At least one of the four ID props is required                                                                                                                                                   |
| `seasonId`           | `number` | **Conditional** | -       | Sportradar season identifier. Shows insights from all matches in this specific season. At least one of the four ID props is required                                                                                                                                                                        |
| `numberOfCards`      | `number` | No              | `15`    | Maximum number of insight cards to display. Must be between 1 and 20                                                                                                                                                                                                                                        |
| `numberOfEvents`     | `number` | No              | -       | Maximum number of distinct matches to include insights from. Must be between 1 and 20. When unset, insights from all available matches are included                                                                                                                                                         |

### Shared Parameters

All of the following parameters from [Bet Insights](https://apidocs.sportradar.com/resources/widgets/docs/bet-insights/bet-insights.md#api-reference) are also supported:

| Property                        | Type             | Default                     | Description                                                                                                                                                            |
| ------------------------------- | ---------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `integration`                   | `string`         | `"inline"`                  | Widget integration mode: `"inline"` or `"button"`                                                                                                                      |
| `cardsLayout`                   | `string`         | `"vertical"`                | Layout direction for insight cards: `"vertical"` or `"horizontal"`                                                                                                     |
| `cardVariant`                   | `string`         | `"default"`                 | Card display style: `"default"`, `"defaultIcon"`, `"compact"`, `"button"`, `"buttonFooter"`, `"single"`                                                                |
| `outcomeOrder`                  | `string`         | `"bottom"`                  | Position of outcome name relative to odds: `"top"`, `"bottom"`. Only applicable with `cardVariant='compact'`, `cardVariant='button'`, and `cardVariant='buttonFooter'` |
| `outcomeButtonPosition`         | `string`         | `"bottom"`                  | Position of call-to-action button in card: `"top"`, `"bottom"`. Not applicable with `cardVariant='button'` and `cardVariant='buttonFooter'`                            |
| `widgetTitle`                   | `string\|false`  | `"Tournament Bet Insights"` | Title displayed in widget header or button label. Set to `false` to hide                                                                                               |
| `widgetIcon`                    | `string\|false`  | Default icon                | URL of custom icon image. Set to `false` to hide                                                                                                                       |
| `disableWidgetHeader`           | `boolean`        | `false`                     | When `true`, hides widget header in inline mode                                                                                                                        |
| `enableCollapse`                | `boolean`        | `true`                      | When `true`, allows collapsing widget content in inline mode. Only applicable when `disableWidgetHeader=false`                                                         |
| `startCollapsed`                | `boolean`        | `false`                     | When `true` and `enableCollapse` is `true`, widget starts collapsed                                                                                                    |
| `modalPosition`                 | `string`         | `"left"`                    | Direction modal opens in button integration: `"left"`, `"right"`, `"bottom"`                                                                                           |
| `modalMaxHeight`                | `number\|string` | -                           | Maximum height of modal content. Allowed units: `%`, `px`, `vh`. Only applicable with `integration='button'` and `cardsLayout='vertical'`                              |
| `isMobile`                      | `boolean`        | `false`                     | When `true`, opens pop-up at viewport bottom edge, full width (only with `integration='button'`)                                                                       |
| `ignoreAdapterValues`           | `boolean`        | `false`                     | When `true`, uses Sportradar standard names instead of adapter-provided translations                                                                                   |
| `capitalizeMarketNameAndOutput` | `boolean`        | `false`                     | When `true`, capitalizes first letter of market and outcome names                                                                                                      |
| `onItemClick`                   | `function`       | -                           | Callback triggered when insight card or outcome is clicked. Receives event and outcome data for bet slip integration                                                   |

> **Note**
>
> `minOdds`, `maxOdds`, and `testMarkets` are available on [Bet Insights](https://apidocs.sportradar.com/resources/widgets/docs/bet-insights/bet-insights.md) only and are **not** supported by Tournament Bet Insights.

## 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](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/SIR.md)

#### HTML/Declarative Integration

- Convert camelCase to lowercase with dashes, e.g. cardVariant becomes card-variant
- Add `data-sr-` prefix
- Example: `cardVariant` → `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`

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. During onboarding our team will review your data, recommend the optimal adapter, and assist with configuration.

Learn more about adapter options [here](https://apidocs.sportradar.com/resources/widgets/docs/adapter/overview.md).

> **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`.

**Generic-Sportradar adapter**

```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 identifiers — provide at least one
        uniqueTournamentId: 27665,
        // tournamentId: 67280,
        // seasonId: 137800,
        // matchId: 67681902,

        // Configuration
        integration: "button",
        modalPosition: "right",
        numberOfCards: 15,

        // ... Optional: Additional customization props (see API Reference)
    };

    SIR('addWidget', '#tournament-bet-insights', 'betInsights.tournament', widgetProps);
</script>

<div id="tournament-bet-insights"></div>
```

**Custom mapping adapter**

```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/sportradar/widgetloader","SIR", {
        language: 'en'
    });

    const widgetProps = {
        // Required identifiers — provide at least one
        uniqueTournamentId: 27665,

        // Configuration
        integration: 'inline',
        numberOfCards: 15,

        onItemClick: function(event, outcomeData) {
            // Add to bet slip
            addToBetSlip({
                matchId: outcomeData.matchId,
                marketId: outcomeData.marketId,
                outcomeId: outcomeData.outcomeId,
                odds: outcomeData.odds
            });
        },
        // ... Optional: Additional customization props (see API Reference)
    };

    SIR('registerAdapter', {})

    SIR('addWidget', '#tournament-bet-insights', 'betInsights.tournament', widgetProps);
</script>

<div id="tournament-bet-insights"></div>
```

**Self-hosted adapter**

```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/sportradar/widgetloader","SIR", {
        language: 'en',
        adapterDataSource: 'spider'
    });

    const widgetProps = {
        // Required identifiers — provide at least one
        uniqueTournamentId: 27665,

        // Configuration
        integration: 'inline',
        numberOfCards: 15,

        onItemClick: function(event, outcomeData) {
            addToBetSlip({
                matchId: outcomeData.matchId,
                marketId: outcomeData.marketId,
                outcomeId: outcomeData.outcomeId,
                odds: outcomeData.odds
            });
        },
        // ... Optional: Additional customization props (see API Reference)
    };

    SIR('addWidget', '#tournament-bet-insights', 'betInsights.tournament', widgetProps);
</script>

<div id="tournament-bet-insights"></div>
```

## Advanced Integration

> **Info**
>
> The examples in this section demonstrate the bet slip integration pattern using the Bet Insights widget. The pattern is identical for Tournament Bet Insights — adapt the identifier prop (`uniqueTournamentId`, `seasonId`, `tournamentId`, or `matchId`) and widget key (`betInsights.tournament`) in your 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.

```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: 67681902,
        onItemClick: onItemClick,
        // ... Additional customization props (see API Reference)
    };

    SIR('addWidget', '#bet-insights', 'betInsights', widgetProps);
```

1. **Step 1**

   Declare `betSlipChangeCallback` to hold a reference to the adapter's update function (assigned later), and `betSlipState` to keep the current list of selections. Both live in the outer scope so the click handler and the adapter endpoint can share them.

2. **Step 2**

   `onItemClick` is called for various widget interactions. When the user clicks an outcome, `target` will be `'externalOutcome'` — that is when you want to add the selection to your bet slip.

3. **Step 3**

   The widget provides the raw external IDs in `data.externalEvent`, `data.externalMarket`, and `data.externalOutcome`. Map these to whatever shape your own bet slip integration expects — the object shown here is just one example format.

4. **Step 4**

   Some markets carry a specifier value (e.g., a handicap or total-goals line). Check for it and add it to the selection object so your bet slip can pinpoint the exact market variant.

5. **Step 5**

   Append the new selection to the existing list and update `betSlipState`. This keeps a local record of everything currently in the bet slip, which you pass to the adapter in the next step.

6. **Step 6**

   Call `betSlipChangeCallback` with the updated state. The adapter registered this callback via the `betSlipSelection` endpoint; invoking it causes the widget to immediately reflect which outcomes are already in the bet slip.

7. **Step 7**

   Pass `onItemClick` as a prop and call `SIR('addWidget', ...)`. From this point the widget renders inside `#bet-insights` and routes every outcome click through your handler.

### 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. See [Handling Selections and Odds](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/handling-selections-and-odds.md) for the shared selection flow, odds snapshot semantics, and validation checklist.

```javascript
{
    onItemClick: function(event, outcomeData) {
        // Add to bet slip
        betSlip.addSelection({
            matchId: outcomeData.matchId,
            marketId: outcomeData.market.id,
            outcomeId: outcomeData.outcome.id,
            odds: outcomeData.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: 67681902,
        onItemClick: onItemClick,
        // ... Additional customization props (see API Reference)
    };

    SIR('addWidget', '#bet-insights', 'betInsights', widgetProps);
```

1. **Step 1**

   Define the adapter (extension) object with a `betSlipSelection` endpoint. The widget calls this endpoint on mount to subscribe to bet slip state changes — it is the bridge between your bet slip and the widget's selection indicators.

2. **Step 2**

   Inside the endpoint, register a listener on your bet slip store. Every time the punter adds or removes a selection, `onBetSlipChange` fires and you push the updated list to the widget via `callback`. Map each entry from your internal store format to the shape the widget expects — the field names shown here (`eventId`, `marketId`, etc.) should match your own store's property names.

3. **Step 3**

   Each selection object must include `type`, `event`, `market`, and `outcome`. The `specifiers` field is optional and only needed for markets with a variant qualifier (e.g., a handicap line). The `odds` object uses `type: 'eu'` for decimal odds — adjust if your store uses a different format.

4. **Step 4**

   Return a cleanup function from the endpoint. The widget calls it when it unmounts, allowing you to unsubscribe from the store and avoid memory leaks.

5. **Step 5**

   Register the adapter once on page load. Only call `SIR('registerAdapter', ...)` once — use the correct form for your adapter type (Generic-Sportradar / Self-hosted, or Custom-mapping).

6. **Step 6**

   `onItemClick` is called when the user interacts with the widget. When `args.type` is `'addSelectionsToBetSlip'`, the user has clicked an outcome card — convert the widget's selection payload to your store's format and add it to the bet slip.

7. **Step 7**

   Pass `onItemClick` as a widget prop and call `SIR('addWidget', ...)` to mount the widget. From this point the widget renders inside `#bet-insights`, stays in sync with your bet slip, and routes outcome clicks through your handler.

### 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
<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: 67681902,
        onItemClick: onItemClick,
        // ... Additional customization props (see API Reference)
    };

    SIR('addWidget', '#bet-insights', 'betInsights', widgetProps);
</script>

<div id="bet-insights"></div>
```

### Tournament Bet Slip Integration — Full Example

The following snippet brings together the widgetloader, the adapter's `betSlipSelection` endpoint, and the `onItemClick` handler specifically for Tournament Bet Insights. For the shared selection flow — snapshot odds, re-pricing, validation, and rejection handling — see [Handling Selections and Odds](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/handling-selections-and-odds.md).

> **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
<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>'
    });

    let betSlipState = { selection: [] };
    let notifyWidget = null;

    const adapter = {
        endpoints: {
            betSlipSelection: (args, callback) => {
                notifyWidget = callback;
                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') {
            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],
            };

            notifyWidget && notifyWidget(undefined, betSlipState);
        }
    }

    const widgetProps = {
        uniqueTournamentId: 27665,  // or tournamentId / seasonId / matchId
        onItemClick: onItemClick,
        numberOfCards: 15,
        // ... Additional customization props (see API Reference)
    };

    SIR('addWidget', '#tournament-bet-insights', 'betInsights.tournament', widgetProps);
</script>

<div id="tournament-bet-insights"></div>
```

## 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](https://apidocs.sportradar.com/resources/widgets/static/img/betInsightsTechnicalGuide.png)

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](https://apidocs.sportradar.com/resources/widgets/static/img/betInsights/betInsightsWidgetBehaviour.png)

When [market status](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#market) 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: "Draw", status: "active", odds: { type: "eu", value: "3.40" } },
        { id: "2", name: "Away", status: "active", odds: { type: "eu", value: "3.50" } }
    ],
    status: "suspended"
};
```

If a [market](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#market) or [outcome](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#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;
    }
}
```

| Class                      | Customization options                                             |
| -------------------------- | ----------------------------------------------------------------- |
| srct-ins-button            | `background-color`, `border-radius`                               |
| srct-ins-button\_\_icon    | `color`                                                           |
| srct-ins-button\_text      | `color`, `font-size`, `font-family`                               |
| srct-ins-header            | `background-color`, `border-color`, `border-width`                |
| srct-ins-header\_\_icon    | `color`                                                           |
| srct-ins-header\_\_text    | `color`, `font-size`, `font-weight`                               |
| srct-ins-header\_\_arrow   | `color`                                                           |
| srct-ins-container         | `background-color`, `font-family`                                 |
| srct-ins-card              | `background-color`, `border-radius`, `border-color`, `box-shadow` |
| srct-ins-text              | `color`, `text-decoration`                                        |
| srct-ins-showmore          | `color`, `font-size`                                              |
| srct-ins-popup             | `background-color`, `color`, `font-size`, `font-family`           |
| srct-ins-popup\_\_icon     | `color`                                                           |
| srct-ins-cta               | `background-color`, `border-color`                                |
| srct-ins-market            | `color`, `font-size`, `font-weight`                               |
| srct-ins-market\_\_icon    | `color`                                                           |
| srct-ins-outcome           | `background-color`, `border-radius`, `padding`                    |
| srct-ins-outcome--selected | `background-color`                                                |
| srct-ins-outcome--disabled | `background-color`                                                |
| srct-ins-outcome\_\_name   | `color`, `font-size`, `font-weight`                               |
| srct-ins-outcome\_\_value  | `color`, `font-size`, `font-weight`                               |
| srct-ins-bubble-count      | `background-color`, `border-color`, `color`                       |
| srct-ins-bubble-count-text | `color`                                                           |
| srct-ins-bubble-new        | `background-color`, `border-color`, `color`                       |
| srct-ins-robot-icon        | `color`                                                           |

### Tournament-specific theming classes

The following classes are specific to the Tournament Bet Insights widget and provide customization for the event header area within each insight card:

| Class                               | Customization options                                      |
| ----------------------------------- | ---------------------------------------------------------- |
| srct-ins-card-header                | `color`, `background-color`, `border-color`, `font-family` |
| srct-ins-card-header\_\_live-label  | `color`, `font-weight`, `font-family`                      |
| srct-ins-card-header\_\_competitors | `color`, `font-size`, `font-weight`                        |
| srct-ins-card-header\_\_datetime    | `color`, `font-size`, `font-weight`                        |
| srct-ins-card-header\_\_result      | `color`, `font-weight`                                     |

## Tips

### Identifier Selection

Choose the right identifier based on your page context:

```javascript
// League overview page — use uniqueTournamentId for all seasons
{ uniqueTournamentId: 27665 }

// Specific season page — use seasonId
{ seasonId: 137800 }

// Tournament page — use tournamentId for a specific instance
{ tournamentId: 67280 }

// Match detail page — use matchId to show insights from other matches in the same tournament
{ matchId: 67681902 }
```

### Controlling Insight Volume

Use `numberOfCards` and `numberOfEvents` to balance coverage and focus:

```javascript
// Focused: many insights from few matches
{
    uniqueTournamentId: 27665,
    numberOfEvents: 3,   // Only next 3 matches
    numberOfCards: 12    // Up to 12 insights total
}

// Broad: fewer insights spread across more matches
{
    uniqueTournamentId: 27665,
    numberOfEvents: 10,  // Up to 10 matches
    numberOfCards: 10    // Only top 10 insights
}

// Maximum coverage
{
    uniqueTournamentId: 27665,
    numberOfCards: 20    // Maximum allowed, no event limit
}
```

### Tournament Hub Integration

Perfect for tournament overview and standings pages:

```javascript
// Tournament hub — button integration in header
SIR('addWidget', '#tournament-header-insights', 'betInsights.tournament', {
    uniqueTournamentId: 27665,
    integration: 'button',
    widgetTitle: 'Best Bets',
    numberOfCards: 10
});

// Tournament page sidebar
SIR('addWidget', '#tournament-sidebar', 'betInsights.tournament', {
    uniqueTournamentId: 27665,
    integration: 'inline',
    cardsLayout: 'vertical',
    cardVariant: 'compact',
    numberOfCards: 12,
    enableCollapse: true
});

// Season standings page
SIR('addWidget', '#standings-insights', 'betInsights.tournament', {
    seasonId: 137800,
    integration: 'inline',
    cardsLayout: 'horizontal',
    numberOfEvents: 5,
    numberOfCards: 10
});
```

### Match Context Display

When using `matchId`, show insights from other tournament matches alongside match-specific insights:

```javascript
// Match detail page — related matches section
SIR('addWidget', '#related-insights', 'betInsights.tournament', {
    matchId: 67681902,
    integration: 'inline',
    widgetTitle: 'Other Tournament Insights',
    numberOfCards: 8
});

// Match-specific insights on the same page
SIR('addWidget', '#match-insights', 'betInsights', {
    matchId: 67681902,
    integration: 'inline',
    widgetTitle: 'This Match Insights'
});
```

### Responsive Design

```javascript
// Desktop — tournament overview
if (window.innerWidth > 1024) {
    SIR('addWidget', '#tournament-insights', 'betInsights.tournament', {
        uniqueTournamentId: 27665,
        integration: 'inline',
        cardsLayout: 'horizontal',
        cardVariant: 'default',
        numberOfCards: 15
    });
}
// Mobile — compact button
else {
    SIR('addWidget', '#tournament-insights', 'betInsights.tournament', {
        uniqueTournamentId: 27665,
        integration: 'button',
        isMobile: true,
        modalPosition: 'bottom',
        cardVariant: 'compact',
        numberOfCards: 10
    });
}
```

## Related Resources

### Bet Insights

Match-level AI betting insights widget with full API reference and advanced integration examples.

[Learn More](https://apidocs.sportradar.com/resources/widgets/docs/bet-insights/bet-insights.md)

### Getting Identifiers

Learn how to obtain tournament, season, and match IDs for widget configuration.

[Learn More](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/getting-identifiers.md)

### Global SIR API

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

[Learn More](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/SIR.md)

### Widget Theming

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

[Learn More](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/theming-widgets.md)

### Handling Selections and Odds

Trust snapshot odds, re-price against your platform, validate availability, and accept or reject bets.

[Learn More](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/handling-selections-and-odds.md)

### Integration Best Practices

Performance optimization, security considerations, and deployment strategies.

[Learn More](https://apidocs.sportradar.com/resources/widgets/docs/tutorials/integration-best-practices.md)
