# Self Service Implementation

### What is a Self-Service Adapter?

A Self-Service Adapter is a custom integration layer that connects SIR Widgets to your betting data sources. Instead of using Sportradar's hosted adapter, you implement the data fetching logic yourself, giving you complete control over:

* **Data Sources** – Connect to any odds/betting API
* **Data Transformation** – Map your API responses to the widget-expected format
* **Caching Strategy** – Implement your own caching and optimization
* **Real-Time Updates** – Control how and when data refreshes

### Understand the Adapter Structure

The adapter object consists of two primary properties:

* **`config`**: Contains static configuration for widgets, such as layout options and filtering rules.
* **`endpoints`**: A collection of functions that the widget calls to request data (e.g., event details, markets, odds).

```javascript
const adapter = {
  config: {
    widget: {
      'betRecommendation.eventList': {
        layout: { /* EventListMarketsConfig */ },
        allowedMarkets: { /* SportMarketsMap */ }
      },
      // ... other widgets
    }
  },
  endpoints: {
    event: (args, callback) => {
      // Fetch and return event data
      // Return optional unsubscribe function
    },
    eventMarkets: (args, callback) => {
      // Fetch and return markets for event
    },
    market: (args, callback) => {
      // Fetch and return specific market
    }
    // ... other endpoints
  }
};
```

[See full type definitions for all endpoints →](https://docs.sportradar.com/engagement-tools/adapter/types)

### Understand the Adapter Data Flow

The following diagram illustrates how data flows from your backend API through the adapter and into the widget.

```mermaid
sequenceDiagram
    participant Widget
    participant Adapter
    participant UserAPI as Your Backend API

    Widget->>Adapter: 1. Request Data (e.g. eventMarkets)
    Adapter->>UserAPI: 2. Fetch or Subscribe to Data (HTTP/WebSocket)
    UserAPI-->>Adapter: 3. Return Raw Data (JSON)
    Adapter->>Adapter: 4. Transform to SIR Format
    Adapter-->>Widget: 5. Return Data via Callback
    Adapter-->>Widget: 6. On new data return it via Callback

```

The adapter acts as a bridge, ensuring that whatever format your API returns is converted into the structure the widget expects.

#### Workflow Breakdown

<figure><img src="https://3613709713-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0TIsdHWTWvAf8JVdDRTe%2Fuploads%2FK1pcTBLmPmm04sCtzaog%2FdataFlowStep1.png?alt=media&#x26;token=02b1217d-069f-48bf-8056-419e34475d9e" alt=""><figcaption></figcaption></figure>

The widget starts by asking the adapter which markets are available for each event using the `availableMarketsForEvent` endpoint. **Example:** The widget requests available markets for Event A, Event B, and Event C.

<figure><img src="https://3613709713-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0TIsdHWTWvAf8JVdDRTe%2Fuploads%2FkpdnRmFca1xjkEYhmwHc%2FdataFlowStep2.png?alt=media&#x26;token=3b2bdea6-a164-49d6-b7b9-8f0174d764b7" alt=""><figcaption></figcaption></figure>

The adapter responds to each request with the available markets for that event. **Example:** For Event A, the adapter returns "1x2" and "Spread" markets. For Event B, only "1x2" is available. For Event C, no data is returned (e.g., event expired). The widget displays placeholders while it loads detailed data.

<figure><img src="https://3613709713-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0TIsdHWTWvAf8JVdDRTe%2Fuploads%2FyFhyB6We93vY5ODhJDiJ%2FdataFlowStep3.png?alt=media&#x26;token=12e7bc1d-03cb-4649-bc28-79ea9d8f825f" alt=""><figcaption></figcaption></figure>

**Data Retrieval Phase:** The widget now requests all the data it needs in parallel. It calls the `market` endpoint for odds and outcomes, the `event` endpoint for event details (like team names and scores), and the `betSlipSelection` endpoint to check the user's current selections. **Example:** For Event A, the widget requests both "1x2" and "Spread" markets. For Event B, it requests "1x2". It also fetches event details for both events and checks the user's bet slip selections.

<figure><img src="https://3613709713-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0TIsdHWTWvAf8JVdDRTe%2Fuploads%2FT5h9Q9CU2VDrxyIKCs05%2FdataFlowStep4a.png?alt=media&#x26;token=a4985df3-c15e-49e8-a814-a9f76be55569" alt=""><figcaption></figcaption></figure>

When the widget receives data from the `event` endpoint, it fills in the placeholders with event information. **Example:** Team names, scores, match period, and start time are displayed.

<figure><img src="https://3613709713-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0TIsdHWTWvAf8JVdDRTe%2Fuploads%2FefX2cFZcB3N4P59qth3q%2FdataFlowStep4b.png?alt=media&#x26;token=e786290b-d728-4ec4-8c40-2ec7da8ba95c" alt=""><figcaption></figcaption></figure>

When the widget receives data from the `market` endpoint, it fills in the odds and outcomes for each market. **Example:** The widget displays the latest odds for each outcome.

<figure><img src="https://3613709713-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2F0TIsdHWTWvAf8JVdDRTe%2Fuploads%2FNiVLZAACtchGTws2KMep%2FdataFlowStep4c.png?alt=media&#x26;token=d5898d5d-77b1-450d-9fcf-92a5be171db0" alt=""><figcaption></figcaption></figure>

&#x20;When the widget receives data from the `betSlipSelection` endpoint, it updates the UI to reflect the user's current selections. **Example:** If the user has already selected outcome 1 for the "1x2" market in Event A, the widget marks it as selected.

Once all endpoint requests have returned data (or timed out), the widget displays the complete information to the user.

***The sequence above is a general example. Each widget has its own unique data pattern, which is detailed in its specific documentation.***

#### Base Implementation Example

Start with this basic HTML structure to implement your self-service adapter.

### Adapter Implementation

The adapter is a JavaScript object that serves as the interface between your application's data layer and the SIR Widgets. It defines how widgets request data and how your application provides it.

#### Implementing the Adapter Endpoints

Now implement the complete adapter with all core endpoints. Here's the full adapter implementation with mock data:

```html
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Self-Service Adapter Demo</title>
    <!-- CSS styles from Step 2 -->
    <script type="text/javascript">
    // Widget loader script from Step 3 

    const adapter = {
      config: {},
      endpoints: {
        availableMarketsForEvent : (args, callback) => {
          callback(undefined, {
            selection: [
              {
                type: "uf",
                event: "61513908",
                market: "1",
              },
            ],
          });
          return () => {};
        },
        eventMarkets: (args, callback) => {
          callback(undefined, {
            event: args.selection.event,
            markets: [
              {
                id: "1",
                status: "active",
                name: "1x2",
                outcomes: [
                  {
                    id: "1",
                    name: "Tenhaisen",
                    odds: {
                      type: "eu",
                      value: "1.88",
                    },
                    status: "active",
                  },
                  {
                    id: "2",
                    name: "draw",
                    odds: {
                      type: "eu",
                      value: "3.85",
                    },
                    status: "active",
                  },
                  {
                    id: "3",
                    name: "Hoftenstain",
                    odds: {
                      type: "eu",
                      value: "3.7",
                    },
                    status: "active",
                  },
                ],
              },
            ],
          });
          return () => {};
        },
        event: (args, callback) => {
          callback(undefined, {
            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,
            },
          });
          return () => {};
        },        
        filterMarkets: (args, callback) => {
          callback(undefined, {
            selection: [
              {
                type: "uf",
                event: "61513908",
                market: "1",
              },
            ],
          });
          return () => {};
        },
        betSlipSelection: (args, callback) => {
          callback(undefined, {
            selection: [
              {
                event: "61513908",
                market: "1",
                outcome: "1",
                type: "uf",
              },
            ],
          });
          return () => {};
        },
        cashBackSelections: (args, callback) => {
          callback(undefined, {
            events: [
              {
                event: "56418457",
                type: "uf",
              },
            ],
          });
          return () => {};
        },
        tickets: (args, callback) => {
          callback(undefined, {
            tickets: [
              {
                ticketId: "ticket_123456",
                bets: [
                  {
                    betId: "bet_001",
                    selections: [
                      {
                        type: "uf",
                        event: "sr:match:12345",
                        market: "1",
                        outcome: "1",
                        odds: { type: "eu", value: "2.10" }
                      }
                    ],
                    stake: [{
                      type: "cash",
                      currency: "USD",
                      amount: "10.00",
                      mode: "total"
                    }]
                  }
                ],
                type: "ticket",
                version: "1.0"
              }
            ]
          });
          return () => {};
        },
      },
    };
    </script>
  </head>
  <body>
    <div class="widgets">
      <div id="sr-widget">Widget will load here...</div>
    </div>
  </body>
</html>
```

#### Example: Mocked Data Adapter

Here's the complete HTML file with all pieces assembled:

**Code Block**

```html
<!DOCTYPE html>
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>Self-Service Adapter Demo</title>
    <style>
      body {
        display: flex;
        justify-content: center;
      }
      .widgets {
        max-width: 620px;
        width: 100%;
      }
      .sr-widget {
        border: rgba(0, 0, 0, 0.12) solid 1px;
      }
    </style>
    <script type="text/javascript">
      (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 global options
      });
      
      const adapter = {
        config: {},
        endpoints: {
          availableMarketsForEvent : (args, callback) => {
            callback(undefined, {
              selection: [
                {
                  type: "uf",
                  event: "61513908",
                  market: "1",
                },
              ],
            });
            return () => {};
          },
          eventMarkets: (args, callback) => {
            callback(undefined, {
              event: args.selection.event,
              markets: [
                {
                  id: "1",
                  status: "active",
                  name: "1x2",
                  outcomes: [
                    {
                      id: "1",
                      name: "Tenhaisen",
                      odds: {
                        type: "eu",
                        value: "1.88",
                      },
                      status: "active",
                    },
                    {
                      id: "2",
                      name: "draw",
                      odds: {
                        type: "eu",
                        value: "3.85",
                      },
                      status: "active",
                    },
                    {
                      id: "3",
                      name: "Hoftenstain",
                      odds: {
                        type: "eu",
                        value: "3.7",
                      },
                      status: "active",
                    },
                  ],
                },
              ],
            });
            return () => {};
          },
          event: (args, callback) => {
            callback(undefined, {
              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,
              },
            });
            return () => {};
          },        
          filterMarkets: (args, callback) => {
            callback(undefined, {
              selection: [
                {
                  type: "uf",
                  event: "61513908",
                  market: "1",
                },
              ],
            });
            return () => {};
          },
          betSlipSelection: (args, callback) => {
            callback(undefined, {
              selection: [
                {
                  event: "61513908",
                  market: "1",
                  outcome: "1",
                  type: "uf",
                },
              ],
            });
            return () => {};
          },
          cashBackSelections: (args, callback) => {
            callback(undefined, {
              events: [
                {
                  event: "56418457",
                  type: "uf",
                },
              ],
            });
            return () => {};
          },
        },
      };
    
      SIR("registerAdapter", adapter);

      window.addEventListener('load', function() {
        SIR("addWidget", "#sr-widget", "betInsights", {
          matchId: 61513908,
          testMarkets: "1"
        });
      });
    </script>
  </head>
  <body>    
    <div class="widgets">
      <div id="sr-widget">Widget will load here...</div>
    </div>
  </body>
</html>
```

#### Connect to Your API

To connect to your actual betting API, you need to replace the mock data with actual network requests. This typically involves fetching data from your backend and transforming it to match the structure expected by the widget.

```javascript
event: (args, callback) => {
  const eventId = args.selection.event;
  
  fetch(`https://your-api.com/events/${eventId}`)
    .then(response => {
      if (!response.ok) throw new Error(`HTTP ${response.status}`);
      return response.json();
    })
    .then(apiData => {
      // Transform your API response to match the expected format
      const eventData = {
        event: {
          id: apiData.id,
          date: {
            displayValue: formatDate(apiData.startTime),
            startTime: apiData.startTime
          },
          sport: { id: apiData.sportId, name: apiData.sportName },
          category: { id: apiData.categoryId, name: apiData.categoryName },
          tournament: { id: apiData.tournamentId, name: apiData.tournamentName },
          teams: apiData.competitors.map(c => ({ id: c.id, name: c.name })),
          isLive: apiData.status === "live"
        }
      };
      callback(undefined, eventData);
    })
    .catch(error => {
      callback(error);
    });
  
  return () => {
    // Cancel pending request if needed
  };
}
```

#### Adding Real-Time Updates

For live events, it is crucial to keep odds and market status up to date. You can achieve this using polling or WebSockets. Here is an example using polling:

```javascript
eventMarkets: (args, callback) => {
  const eventId = args.selection.event;
  let intervalId;
  
  const fetchMarkets = () => {
    fetch(`https://your-api.com/events/${eventId}/markets`)
      .then(res => res.json())
      .then(data => {
        callback(undefined, transformMarketsData(data));
      })
      .catch(err => callback(err));
  };
  
  // Initial fetch
  fetchMarkets();
  
  // Poll every 3 seconds for live updates
  intervalId = setInterval(fetchMarkets, 3000);
  
  // Return cleanup function
  return () => {
    clearInterval(intervalId);
  };
}
```

### Further Reading

* [Adapter API Reference](https://docs.sportradar.com/engagement-tools/adapter/types) – Complete type definitions for all endpoints
* [Adapter Overview](https://docs.sportradar.com/engagement-tools/adapter/overview) – Architecture and concepts
* [Self-Service Adapter Reference](https://docs.sportradar.com/engagement-tools/adapter/self-service-implementation) – Detailed endpoint documentation
* [Hosted Adapter](https://docs.sportradar.com/engagement-tools/adapter/hosted-implementation) – Alternative approach using Sportradar-managed adapter

### Widget-Specific Tutorials

Once you've completed this general integration tutorial, refer to widget-specific guides for detailed implementation patterns:

* Bet Insights Widget – *Coming soon*
* Bet Recommendation Widgets – *Coming soon*
* Swipe Bet Widget – *Coming soon*
