An adapter is a piece of code that acts as a bridge between two systems. In the context of Bet Suggestions, an adapter connects your data source (such as a backend API or database) to the Suggested Bets feature in the Bet Concierge displayed to users.
For more general information about Sportradar adapters see Adapter Overview.
When you register an adapter, it listens for specific endpoints from the widget:
| Adapter Endpoint | Description | Type |
|---|---|---|
availableMarketsForEvent | The Bet Concierge AI assistant will suggest bet from one of these markets based on conversation context. | AvailableMarketsForEvent |
market | Get full market information about market that AI suggested. Implement either this OR eventMarkets. | Market |
eventMarkets | Get full market information about market that AI suggested. Implement either this OR market. | EventMarkets |
betSlipSelection | Matches selections to the user's bet slip. Outcome on bet suggestion will be marked as selected | BetSlipSelection |
recommendedSelections | Will mark selection as recommended. Outcome on suggested bet market will be marked as suggested - use for the promotions. | RecommendedSelections |
calculateCustomBetXML | Prepares custom bet XML payload for UOF odds calculation. Endpoint for custom bet feature. | CalculateCustomBetXML |
event | Retrieves detailed event data including event start time, scores, team names, tournament name, and more. | Event |
The example below implements all Bet Concierge adapter endpoints with mocked data so it works out of the box. In a production environment, replace the mocked data with calls to your own API. All markets must be mapped to Sportradar market IDs.
The widget subscribes to market data through the eventMarkets callback. When the widget first requests markets, save the callback and args so you can push updated odds or market status changes later — for example when your WebSocket feed reports a price change or when the user triggers a manual refresh.
The handleRefreshOdds() function in the example below demonstrates this pattern: it re-fetches market data from your API and re-invokes the saved callback with the latest values. Wire this function to your odds feed, polling interval, or any other update mechanism in your application.
SIR Widgets supports two types of adapter implementations:
<script>
(function(a,b,c,d,e,f,g,h,i){a[e]||(i=a[e]=function(){(a[e].q=a[e].q||[]).push(arguments)},i.l=1*new Date,i.o=f,
g=b.createElement(c),h=b.getElementsByTagName(c)[0],g.async=1,g.src=d,g.setAttribute("n",e),h.parentNode.insertBefore(g,h)
)})(window,document,"script","https://widgets.sir.sportradar.com/sportradar/widgetloader","SIR", {
language: 'en'
});
const MATCH_ID = "sr:match:50955863";
// Saved callback and args for pushing live odds updates to the widget
let eventMarketsCallback = null;
let eventMarketsArgs = null;
// Replace with your own API client
const clientApi = {
fetchFixtureMarkets: (eventId, language) =>
Promise.resolve([
{
id: "1",
name: "1x2",
status: "active",
outcomes: [
{ id: "1", name: "Home", status: "active", odds: { type: "eu", value: "1.48" } },
{ id: "2", name: "Draw", status: "active", odds: { type: "eu", value: "4.82" } },
{ id: "3", name: "Away", status: "active", odds: { type: "eu", value: "2.47" } },
],
},
{
id: "18",
name: "Total goals",
specifiers: "total=2.5",
status: "active",
outcomes: [
{ id: "13", name: "Under 2.5", status: "active", odds: { type: "eu", value: "2.25" } },
{ id: "12", name: "Over 2.5", status: "active", odds: { type: "eu", value: "1.80" } },
],
},
]),
};
// Map your API market objects to Sportradar market IDs and adapter format
function mapMarkets(markets, oddsType) {
return markets.map((market) => ({
id: market.id,
name: market.name,
status: market.status,
specifiers: market.specifiers,
outcomes: market.outcomes.map((outcome) => ({
id: outcome.id,
name: outcome.name,
status: outcome.status,
odds: { type: oddsType, value: outcome.odds.value },
})),
}));
}
function mapToSrMarketIds(markets) {
return markets.map((market) => ({
srMarketId: market.id,
specifiers: market.specifiers,
}));
}
// Map a bet slip selection from your format to Sportradar adapter format
function mapToSrSelection(selection) {
return {
type: "uf",
event: selection.eventId,
market: selection.marketId,
outcome: selection.outcomeId,
specifiers: selection.specifiers,
odds: { type: "eu", value: selection.odds },
};
}
// Replace with your page bet slip module
const pageBetslip = {
getUserSelections: () => [],
betSlipSelectionCallback: null,
};
const adapter = {
endpoints: {
// Mandatory: lists markets the AI assistant can suggest from
availableMarketsForEvent: (args, callback) => {
const { event } = args.selection;
clientApi
.fetchFixtureMarkets(event)
.then(mapToSrMarketIds)
.then((srMarkets) => {
callback(undefined, {
selection: srMarkets.map(({ srMarketId, specifiers }) => ({
type: "uf",
event,
market: srMarketId,
specifiers,
})),
});
})
.catch(callback);
return () => {};
},
// Mandatory: returns full market data when the AI suggests a market
// Save callback and args so you can push live odds updates later
eventMarkets: (args, callback) => {
eventMarketsCallback = callback;
eventMarketsArgs = args;
const { event } = args.selection;
const { language, oddsType } = args;
clientApi
.fetchFixtureMarkets(event, language)
.then((markets) => {
callback(undefined, {
event,
markets: mapMarkets(markets, oddsType),
});
})
.catch(callback);
return () => {
eventMarketsCallback = null;
eventMarketsArgs = null;
};
},
// Mandatory: marks outcomes already in the user's bet slip as selected
betSlipSelection: (_args, callback) => {
// Subscribe to bet slip changes — pageBetslip calls this when selections update
pageBetslip.betSlipSelectionCallback = (userSelections) => {
const selection = userSelections.map(mapToSrSelection);
callback(undefined, { selection });
};
// Return current bet slip selections immediately
const userSelections = pageBetslip.getUserSelections();
const selection = userSelections.map(mapToSrSelection);
callback(undefined, { selection });
return () => {
pageBetslip.betSlipSelectionCallback = null;
};
},
// Optional: marks a selection as recommended (use for promotions)
recommendedSelections: (args, callback) => {
callback(undefined, {
selection: [
{ type: "uf", event: MATCH_ID, market: "1", outcome: "1" },
],
});
return () => {};
},
// Optional: enables custom bet combo suggestions (requires enableCustomBet: true)
calculateCustomBetXML: (args, callback) => {
fetch("{url}/api/custombet/calculate-filter", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: args.calculatePayload,
})
.then((response) => response.text())
.then((payload) => callback(undefined, { payload }))
.catch(callback);
return () => {};
},
},
};
SIR("registerAdapter", adapter);
// Call this when odds change — wire it to your WebSocket feed, polling, or manual refresh
function handleRefreshOdds() {
if (!eventMarketsCallback || !eventMarketsArgs) return;
const { event } = eventMarketsArgs.selection;
const { language, oddsType } = eventMarketsArgs;
clientApi
.fetchFixtureMarkets(event, language)
.then((markets) => {
eventMarketsCallback(undefined, {
event,
markets: mapMarkets(markets, oddsType),
});
})
.catch((err) => eventMarketsCallback?.(err));
}
async function getJwt() {
const response = await fetch("/api/get-token");
const data = await response.json();
return data.jwt;
}
SIR("addWidget", ".sr-bc-widget", "betConciergeNew", {
entityId: MATCH_ID,
getJwt: getJwt,
enableCustomBet: true,
});
</script>
<div class="bc-wrapper">
<div class="sr-bc-widget"></div>
</div>