---
title: "Bet Concierge Data Adapter"
canonical_url: "https://apidocs.sportradar.com/resources/widgets/docs/bet-concierge/data-adapter"
markdown_url: "https://apidocs.sportradar.com/resources/widgets/docs/bet-concierge/data-adapter.md"
last_updated: "2026-08-14T12:48:01Z"
---

# Bet Concierge Data Adapter

## What Is an Adapter?

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

## How Does an Adapter Work?

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](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#availablemarketsforevent-function) |
| `market`                   | Get full market information about market that AI suggested. **Implement either this OR `eventMarkets`**.                  | [Market](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#market-function)                                     |
| `eventMarkets`             | Get full market information about market that AI suggested. **Implement either this OR `market`**.                        | [EventMarkets](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#eventmarkets-function)                         |
| `betSlipSelection`         | Matches selections to the user's bet slip. Outcome on bet suggestion will be marked as selected                           | [BetSlipSelection](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#betslipselection-function)                 |
| `recommendedSelections`    | Will mark selection as recommended. Outcome on suggested bet market will be marked as suggested - use for the promotions. | [RecommendedSelections](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#recommendedselections-function)       |
| `calculateCustomBetXML`    | Prepares custom bet XML payload for UOF odds calculation. Endpoint for custom bet feature.                                | [CalculateCustomBetXML](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#calculatecustombetxml-function)       |
| `event`                    | Retrieves detailed event data including event start time, scores, team names, tournament name, and more.                  | [Event](https://apidocs.sportradar.com/resources/widgets/docs/adapter/Types.md#event-function)                                       |

### Adapter Implementation Template

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](https://apidocs.sportradar.com/resources/widgets/docs/bet-concierge/market-mapping.md).

#### Pushing live odds updates

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.

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

## How to Register an Adapter

SIR Widgets supports two types of adapter implementations:

1. [Self-Service Adapter](https://apidocs.sportradar.com/resources/widgets/docs/adapter/SelfServiceImplementation.md)
2. [Hosted Adapter](https://apidocs.sportradar.com/resources/widgets/docs/adapter/HostedImplementation.md)
