Similar Bets widget (BetSlip Recommendation) is a front-end widget that provides bet recommendations based on existing selection in the bet slip.
Similarity requirement
This widget requires at least one event ID in the similarEventIds array to generate recommendations. It analyzes these events to find matches with similar characteristics (sport, status, betting patterns).
Recommendation type
Integration options
Smart AI-driven recommendations based on the punter's most recent and relevant bet slip activity.
Seamless support for both real-time in-play event suggestions and upcoming pre-match selection boosts.
Deeply integrated user experience that feels like a natural extension of the operator's checkout flow.
Designed to sit either next to or directly inside the bet slip, adapting to various sidebar and modal widths.
Fully adaptive design that maintains high-density information clarity across desktop and mobile viewports.
Complete creative control over colors, typography, and corner radius to match your platform's design system.
Access to the client API
Required endpoints: Odds, Match IDs of selections currently in the bet slip
Requires an adapter to be registered via SIR('registerAdapter', '{ADAPTER_NAME}'). See the adapter overview: https://apidocs.sportradar.online/resources/widgets/docs/adapter/Overview
widget-name: betRecommendation.similarBets
Environment Requirements
Supported Sports
See the Similar Bets widget demo. Illustrations of main configurations with relevant property values below.

The filters object provides optional filtering to refine similar event recommendations. Note: recommendationType is automatically set to "similar" and cannot be changed.
| Property | Type | Default | Description |
|---|---|---|---|
sport | object | undefined | Sport filter configuration. |
sport.available | array<string|number> | [] | Array of Sportradar sport IDs to limit similar event recommendations. Empty array or omitted shows similar events from all sports. Example: [1] restricts to soccer similar events only. Useful when bet slip contains single-sport selections and you want to maintain sport consistency. See Sports Reference. |
sport.sportNames | boolean | false | Display mode for sport indicators. When true, shows sport names as text labels. When false, shows sport-specific icons. Typically keep false for compact display in bet slip. |
time | object | undefined | Time/status filter configuration. |
time.available | array<string> | [] | Array of time filter options. Options: "live" (only live similar events), "not_started" (only upcoming similar events). Example: ["not_started"] shows only upcoming similar events. Useful to match bet slip event timing (if bet slip has only pre-match bets, show only pre-match similar events). |
time.active | array<string> | [] | Initially active time filters. Can select multiple from available array. Example: ["not_started"] starts showing only upcoming events. |
country | object | undefined | Country/region filter configuration. |
country.available | array<string|number> | undefined | Array of country identifiers to limit similar events geographically. Example: ["GB", "DE", "ES"] shows only similar events from UK, Germany, Spain. Useful for geo-compliance or regional betting preferences. See Getting Identifiers. |
league | object | undefined | League/tournament filter configuration. |
league.available | array<string|number> | undefined | Array of tournament/league identifiers to limit similar events by competition. Example: ["sr:tournament:17", "sr:tournament:34"] shows only similar events from Premier League and Bundesliga. Useful when bet slip contains events from specific leagues. See Getting Identifiers. |
{
sport: {
available: [1, 2], // Soccer and Basketball only
sportNames: false
},
time: {
available: ['not_started'],
active: ['not_started'] // Only upcoming similar events
},
country: {
available: ['GB', 'DE', 'ES', 'IT'] // European countries
},
league: {
available: [
'sr:tournament:17', // Premier League
'sr:tournament:34', // Bundesliga
]
}
}Theming customization allows to tailor the appearance of Bet Recommendation widgets to meet specific needs and preferences. In the context of the Bet Recommendation widget, customization refers to the ability to modify the default styling of the widget by applying custom CSS properties to the various HTML elements that make up the widget.
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.
All custom classes must be nested within the .sr-bb.sr-br-betslip selector class. This ensures that the custom styles only apply to that widget and not to other elements on the page.
| CSS class | Supported CSS properties |
|---|---|
srct-br-container | background-color, font-family |
srct-br-footer | color |
srct-br-loading | background-color, border-radius |
srct-br-content | background-color |
srct-br-content__title | color |
srct-br-cardlist | gap |
srct-br-cardlist__divider | border-color, border-width |
srct-br-card | background-color, color, border-radius |
srct-br-card__divider | border-color |
srct-br-outcome | background-color, color, border-radius |
srct-br-outcome--selected | background-color, color, border-radius |
srct-br-outcome--disabled | background-color, color, border-radius |
srct-br-outcome__name | font-size, color |
srct-br-outcome__value | font-size, color |
srct-br-eventinfo | font-size, color |
srct-br-eventinfo__info | font-size |
srct-br-eventinfo__icon | color |
srct-br-eventinfo__time | font-size, color |
srct-br-eventinfo__status | font-size, color |
srct-br-eventinfo__name | font-size, color |
srct-br-scoreboard | font-size, color |
srct-br-scoreboard__teams | font-size, color |
srct-br-scoreboard__scores | font-size, color |
srct-br-scoreboard__score-1 | font-size, color |
srct-br-scoreboard__score-2 | font-size, color |
Mandatory Configuration
The filters property is required for this widget to function correctly. It defines the recommendation logic and basic data constraints.
Similar Recommendations Requirement
When using the similar recommendation type, you must provide the similarEventIds identifier to specify the reference matches. Refer to Getting Identifiers for more details.

Demo: Open demo
Basic bet slip sidebar integration using current event IDs from the user's bet slip.
JavaScript
const betSlipEventIds = betSlip.getEventIds();
SIR("addWidget", "#bet-slip-recommendations", "betRecommendation.similarBets", {
similarEventIds: betSlipEventIds,
count: 3,
});HTML (data attributes)
<div
class="sr-widget"
data-sr-widget="betRecommendation.similarBets"
data-similar-event-ids="[12345, 67890, 11111]"
data-count="3"
></div>The onItemClick callback is fired whenever the user interacts with the widget. The first argument is a target string that identifies the interaction type; the second argument is a data object containing contextual information.
target value | Trigger | Key data properties |
|---|---|---|
"externalOutcome" | User clicks a single outcome button | externalEvent, externalMarket, externalOutcome |
"externalOutcomes" | User clicks multiple outcomes at once (e.g. combo card) | Array of { externalEvent, externalMarket, externalOutcome } |
"externalEvent" | User clicks an event header/card | externalEvent |
"externalCompetition" | User clicks a competition/league name | externalCompetition |
"goToBetSlip" | User clicks the "Go to Bet Slip" button (swipeBet only) | — |
"betSlipMode" | Bet slip mode changes between single and multi (swipeBet only) | value: "single" | "multi" |
SIR("addWidget", "#sr-widget", "betRecommendation.markets", {
onItemClick: function (target, data) {
if (target === "externalOutcome") {
// Add single outcome to bet slip
const { externalEvent, externalMarket, externalOutcome } = data;
betSlip.add({
eventId: externalEvent.id,
marketId: externalMarket.id,
outcomeId: externalOutcome.id,
});
} else if (target === "externalEvent") {
// Navigate to event/match detail page
window.location.href = `/matches/${data.externalEvent.id}`;
} else if (target === "externalCompetition") {
// Navigate to competition/league page
window.location.href = `/league/${data.externalCompetition.tournament.id}`;
}
},
filters: { recommendationType: { available: "popular" } },
});Note: Widgets support callbacks on outcome clicks — the onItemClick handler receives target === "outcome" and a data object containing externalEvent, externalMarket and externalOutcome. Use this for custom outcome callbacks (e.g., add-to-betslip, analytics, modals).
The widget also exposes onTrack for event tracking analytics. See the tracking guide for details.
To keep the widget's selected-outcome state in sync with your own bet slip (i.e. show outcomes as selected when they were added outside the widget), use registerOnBetSlipChange inside registerAdapter.
// 1. Track your bet slip state
let changeCallback;
let betSlipState = { betslip: [], combinedOddsValue: undefined };
// 2. Notify the widget whenever the bet slip changes
function onBetSlipChanged(callback) {
changeCallback = callback;
changeCallback && changeCallback(betSlipState); // push current state immediately
}
// 3. Handle outcome clicks from the widget
function onItemClick(target, data) {
if (target === "externalOutcome") {
const newBet = {
externalEventId: data.externalEvent.id,
externalMarketId: data.externalMarket.id,
externalOutcomeId: data.externalOutcome.id,
externalSpecifierId: data.externalMarket.specifier
? data.externalMarket.specifier.value
: undefined,
};
betSlipState = {
betslip: [...betSlipState.betslip, newBet],
combinedOddsValue: "14.52", // replace with your own calculation
};
changeCallback && changeCallback(betSlipState);
}
}
// 4. Register adapter with bet slip sync + add the widget
SIR("registerAdapter", "{ADAPTER_NAME}", { onBetSlipChanged });
SIR("addWidget", "#sr-widget", "betRecommendation.markets", { onItemClick });Until a custom adapter is developed, use the mockData adapter for local testing:
SIR("registerAdapter", "mockData", { onBetSlipChanged });An adapter is a software component developed by the Sportradar engineering team that bridges the Bet Recommendation widgets and your platform's API. It retrieves data from your API and feeds it to the widget, ensuring seamless communication between the two systems.
Before adapter development begins, confirm and align your API contract with the Sportradar engineering team. Integration requires two SIR calls:
| SIR method | Purpose |
|---|---|
SIR('registerAdapter', ...) | Configure the adapter that retrieves and displays data from your API. |
SIR('addWidget', ...) | Mount the widget on the page. |
(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" },
);
SIR("registerAdapter", "{ADAPTER_NAME}");
SIR("addWidget", "#sr-widget", "betRecommendation.markets");The following data types are provided by the adapter and are also available in the onItemClick callback payload.
Event
| Property | Type | Required | Description |
|---|---|---|---|
id | string | number | Yes | Sportradar event ID. |
externalId | string | number | — | Client-side event ID. |
date | string | Yes | Formatted date string displayed in the widget. |
sport.id | string | number | Yes | Sport ID. Use sportsMapping if not using Sportradar sport IDs. |
sport.name | string | Yes | Sport name. |
category.id | string | number | — | Category ID. |
category.name | string | Yes | Category name (e.g. "England"). |
tournament.id | string | number | — | Tournament/league ID. |
tournament.name | string | Yes | Tournament name. |
teams | Array<{id, name}> | Yes | Home and away competitors. |
isLive | boolean | Yes | Whether the event is currently live. |
liveCurrentTime | string | Yes | Live time display (e.g. "2nd set", "45'"). |
result1 / result2 / result3 | result | — | Score columns: { result: [homeScore, awayScore] }. |
Market
| Property | Type | Required | Description |
|---|---|---|---|
id | string | number | Yes | Market ID. |
name | string | — | Market name (e.g. "Match Winner"). |
status.isActive | boolean | — | When false, see Widget Behavior. |
Outcome
| Property | Type | Required | Description |
|---|---|---|---|
id | string | number | Yes | Outcome ID. |
name | string | Yes | Outcome name (e.g. "Home", "Draw"). |
odds | string | number | Yes | Odds value. Use a number type to enable odds-change indicators (up/down arrows). |
specifier.value | string | number | — | Additional specifier (e.g. handicap value "-2.50"). |
status.isActive | boolean | — | When false, see Widget Behavior. |
similarEventIds ManagementRe-render the widget whenever the bet slip changes to keep recommendations in sync:
betSlip.on("selectionAdded", updateSimilarBets);
betSlip.on("selectionRemoved", updateSimilarBets);
betSlip.on("selectionChanged", updateSimilarBets);
betSlip.on("cleared", hideSimilarBets);
function updateSimilarBets() {
const eventIds = betSlip.getEventIds();
if (eventIds.length === 0) {
hideSimilarBets();
return;
}
SIR("addWidget", "#similar-bets", "betRecommendation.similarBets", {
similarEventIds: eventIds,
count: 3,
});
}Debounce updates if the bet slip fires multiple events in quick succession:
let updateTimer;
function debouncedUpdateSimilarBets() {
clearTimeout(updateTimer);
updateTimer = setTimeout(() => {
updateSimilarBets();
}, 500);
}
betSlip.on("selectionAdded", debouncedUpdateSimilarBets);
betSlip.on("selectionRemoved", debouncedUpdateSimilarBets);Limit recommendations as the bet slip grows, and hide the widget when it is full:
function updateSimilarBets() {
const selections = betSlip.getSelections();
const eventIds = selections.map((s) => s.eventId);
if (eventIds.length === 0) {
hideSimilarBets();
return;
}
if (eventIds.length >= 5) {
hideSimilarBets();
return;
}
const recommendationCount = Math.min(3, 5 - eventIds.length);
SIR("addWidget", "#similar-bets", "betRecommendation.similarBets", {
similarEventIds: eventIds,
count: recommendationCount,
});
}