---
title: "iFrame/Modal integration Guide"
canonical_url: "https://apidocs.sportradar.com/resources/statshub/docs/iframe-modal-integration-guide"
markdown_url: "https://apidocs.sportradar.com/resources/statshub/docs/iframe-modal-integration-guide.md"
last_updated: "2026-07-15T10:16:29Z"
---

# iFrame/Modal integration Guide

StatsHub is an "out-of-the-box," fully hosted fan-engagement solution that provides real-time live
scores and deep sports statistical analysis. Because Sportradar hosts, develops, and maintains all of the
backend infrastructure and data pipelines, there is no technical overhead or maintenance required on
your side. The solution is delivered as a hosted application URL.
This guide outlines how to integrate StatsHub seamlessly into your sports betting or media website
using an HTML `<iframe>`.

## URL Structure & Client Configuration

Your customized StatsHub application is accessible via a unique URL configured by the Sportradar
integration team. The standard URL format is:

> **URL Format**
>
> <https://statshub.sportradar.com/{client_alias}/{language}>

- **client\_alias**: Your unique bookmaker/media identifier (provided during setup).
- **language**: The localized display language (e.g., en, de). StatsHub natively supports over 40 languages.

**Example Baseline URL: <https://statshub.sportradar.com/sportradar/en>**

## Basic iFrame Implementation

To display the StatsHub module directly within your container page, place the following <iframe>
block inside your HTML:

```html
<div class="statshub-container">
<iframe
  id="statsIframe"
  src="https://statshub.sportradar.com/{your_alias}/{language}/sport/1"
  scrolling="yes"
  style="width: 100%; border: none;">
</iframe>
</div>
```

## CSS Requirements & Responsive Behavior

StatsHub is fully responsive and auto-adapts dynamically to any parent container width. For a
responsive, scroll-friendly integration across desktop and mobile browsers, use the following CSS
structure:

```css
/* Container sizing */
.statshub-container {
  width: 100%;
  overflow: hidden;
}

/* Iframe layout optimization */
#statsIframe {
  width: 100%;
  border: none;
  height: 100%;
}
```

> **Info**
>
> **Scrolling: The scrolling="yes" attribute must be enabled.**

## Required Security Setup: Domain Whitelisting

Sportradar enforces strict security and frame-ancestor constraints on the StatsHub platform.

> **Info**
>
> **The Rule: If your web application loads StatsHub inside an \<iframe>, the parent page's
> top-level domain must be whitelisted within our platform.**

 

If no domains are explicitly configured, the application will fail to render, and browsers will block the
frame due to Content Security Policy frame-ancestors violations. Contact the client setup team to
configure the domains.

## Common Gotchas & Troubleshooting

### Troubleshooting CSP and Connection Errors

If you encounter a blank screen or connection errors in your browser console (e.g., “Refused to
connect to statshub.sportradar.com” or “Firefox can’t open this page”), please verify your domain
configuration.

#### Verify Your Configuration

- Exact Domain Match: Ensure the domain of your parent page matches the exact URL you submitted to the Sportradar client setup team.
- Protocols: The protocol (http vs. https) must match exactly.
- Subdomains: Subdomains are not automatically included.

#### Common Origin Patterns

- Standard Domain: <https://example.com>
- Wildcard Subdomains: https\://\*.example.com (matches <https://www.example.com>,
  <https://test.example.com>, etc.)
- Local Development: <http://localhost:3000>
- Local Files: file://\*
- Mobile/Custom Frameworks: capacitor://localhost (used by frameworks like Ionic for
  custom webview protocols)

If the issue persists, copy the error from your browser's developer console and send it to our
support team.

### Post-Message Interactions

Please note that StatsHub operates as a secure, independent hosted system. Interactive buttons inside
the application framework (such as favourite pinning or bookmarking) execute within the StatsHub
application context and does not trigger *custom window\.postMessage* events back to your parent
application

## StatsHub Modal Window Integration Guide

Integrating StatsHub inside a modal window (also referred to as a lightbox or pop-up overlay) is the
preferred implementation for sportsbooks and media portals that want to keep users on their active
match list or betting sheet while offering immediate, deep statistical analysis.

This integration uses a lightweight trigger button on your parent page that dynamically injects the
appropriate StatsHub deep-link into a hidden overlay containing an \<iframe>.

## Interaction Flow & UX Specifications

Following Sportradar's standard design standards:

- **Desktop Viewports**: StatsHub renders as a centered, high-contrast modal or side-drawer
  overlay with a subtle backdrop overlay.
- **Mobile Viewports (Breakpoints < 768px)**: To optimize small screen experiences, the modal
  dynamically switches to a **full-screen viewport overlay** (100% width and height) to maximize
  readable area and prevent tabs from being cut off.
- **Closing**: An explicit **"X" Close Button** is positioned in the top-right corner of the
  modal wrapper to unload the iframe and return the user to their scroll position.

## HTML Structure

Add the hidden modal container directly before the closing </body> tag of your main page. This
structure isolates the modal backdrop, the close button, and the iframe wrapper:

```html
<!-- Trigger Button Example (e.g., in your Match List) -->
<button class="stats-trigger-btn" data-match-id="61513882" onclick="openStatsModal(this)">
View Stats
</button>
<!-- StatsHub Modal Window Container -->
<div id="statsHubModal" class="statshub-modal-overlay" aria-hidden="true" style="display:
none;">
<div class="statshub-modal-wrapper">
<!-- Close Button -->
<button class="statshub-close-btn" onclick="closeStatsModal()" aria-label="Close
Statistics">&times;</button>
<!-- Iframe Loader Container -->
<div class="statshub-iframe-container">
<iframe
id="modalIframe"
src="about:blank"
scrolling="yes"
frameborder="0"
style="width: 100%; height: 100%; border: none;">
</iframe>
</div>
</div>
```

## Responsive CSS (Desktop & Mobile)

To prevent visual bugs, such as double scrollbars on mobile or layout cut-offs, use this unified CSS
stylesheet. It enforces full-screen rendering on mobile and applies a standard light-box constraint on
desktop screens.

```css
/* Modal backdrop/overlay */
.statshub-modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.75);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 9999;
  backdrop-filter: blur(4px);
}

/* Modal box wrapper */
.statshub-modal-wrapper {
  position: relative;
  background-color: #1a1a1a; /* Match your bookmaker platform theme */
  width: 85%;
  height: 85%;
  border-radius: 8px;
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
  overflow: hidden;
  display: flex;
  flex-direction: column;
}

/* Iframe container */
.statshub-iframe-container {
  width: 100%;
  flex-grow: 1;
  height: 100%;
}

/* Float Close Button on top-right */
.statshub-close-btn {
  position: absolute;
  top: 15px;
  right: 20px;
  background: rgba(0, 0, 0, 0.5);
  border: none;
  color: #ffffff;
  font-size: 30px;
  font-weight: bold;
  cursor: pointer;
  z-index: 10001;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background 0.2s ease;
}

.statshub-close-btn:hover {
  background: rgba(255, 255, 255, 0.2);
}

/* Mobile Responsiveness Breakpoint: Full screen overlay */
@media (max-width: 767px) {
  .statshub-modal-wrapper {
    width: 100% !important;
    height: 100% !important;
    height: 100dvh !important; /* Prevents double scrolling in iOS/Android */
    border-radius: 0;
    padding: 0 !important;
  }
  
  .statshub-close-btn {
    top: 10px;
    right: 10px;
    width: 35px;
    height: 35px;
    font-size: 24px;
  }
}
```

## JavaScript Controller

This script dynamically generates the secure StatsHub deep-link when a match is selected, populates
the iframe, handles the backdrop display, and freezes the parent page's scrolling behavior to prevent
"double scrollbar" bugs on mobile:

```javascript
const CLIENT_ALIAS = 'your_alias'; // Replace with your Sportradar alias
const LANGUAGE = 'en'; // Set dynamic display language

function openStatsModal(buttonElement) {
  const matchId = buttonElement.getAttribute('data-match-id');
  if (!matchId) {
    console.error("Match ID is missing from trigger button");
    return;
  }
  const modal = document.getElementById('statsHubModal');
  const iframe = document.getElementById('modalIframe');

  // 1. Dynamically construct the StatsHub Deep-Link URL (no 'm' prefix required)
  const statsHubUrl = `https://statshub.sportradar.com/${CLIENT_ALIAS}/${LANGUAGE}/match/${matchId}`;

  // 2. Inject URL and display modal
  iframe.src = statsHubUrl;
  modal.style.display = 'flex';
  modal.setAttribute('aria-hidden', 'false');
  // 3. Prevent parent background body scrolling while modal is open
  document.body.style.overflow = 'hidden';
  }

function closeStatsModal() {
  const modal = document.getElementById('statsHubModal');
  const iframe = document.getElementById('modalIframe');

  // 1. Hide modal container
  modal.style.display = 'none';
  modal.setAttribute('aria-hidden', 'true');// 2. Reset iframe destination immediately to stop streams and clear memory
  iframe.src = 'about:blank';

  // 3. Restore parent body scrolling
  document.body.style.overflow = '';
}

// Close modal if user clicks on the darkened backdrop outside the wrapper
window.addEventListener('click', function(event) {
  const modal = document.getElementById('statsHubModal');
  if (event.target === modal) {
    closeStatsModal();
  }
});
```

## Mobile Viewport Height Handling

Mobile web browsers (especially Safari on iOS) handle *100vh* poorly due to collapsing address bars,
which historically causes double-scrollbars inside modals. Enforcing *height: 100dvh* on your
wrapper ensures that the modal occupies the exact dynamic viewport size.

## Full iFrame Integration Example

**A full integration example**

```md
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>StatsHub iFrame Integration</title>
    <style>
      *,
      *::before,
      *::after {
      }
      box-sizing: border-box;
      html,
      body {
        height: 100%;
      }
      body {
        margin: 0;
        display: flex;
        flex-direction: column;
        height: 100vh;
        height: 100dvh;
      }
      font-family:
        system-ui,
        -apple-system,
        sans-serif;
      background: #0f0f0f;
      color: #e5e5e5;
      /* Example parent header — replace `.topbar` selector in JS if yours differs */
      .topbar {
        display: flex;
        flex-shrink: 0;
        align-items: center;
        justify-content: space-between;
        gap: 1rem;
        padding: 0 1.25rem;
        height: 60px;
        background: #1a1a1a;
        border-bottom: 1px solid #2a2a2a;
      }
      transition:
        height 0.2s ease,
        padding 0.2s ease;
      .topbar--tall {
        height: 120px;
        flex-wrap: wrap;
        align-content: center;
      }
      padding: 1rem 1.25rem;
      .header-size-toggle {
        display: inline-flex;
        align-items: center;
      }
      gap: 0.5rem;
      font-size: 0.875rem;
      cursor: pointer;
      user-select: none;
      .topbar h1 {
        margin: 0;
      }
      font-size: 1.125rem;
      font-weight: 600;
      .topbar-actions {
      }
      display: flex;
      flex-wrap: wrap;
      gap: 0.5rem;
      .stats-trigger-btn {
        padding: 0.5rem 0.875rem;
        border: 1px solid #3a3a3a;
        border-radius: 6px;
        background: #252525;
        color: #fff;
        font-size: 0.875rem;
        cursor: pointer;
      }
      transition: background 0.2s ease;
      .stats-trigger-btn:hover {
        background: #333;
      }
      .statshub-container {
        flex: 1;
        min-height: 0;
        width: 100%;
        overflow: hidden;
      }
      #statsIframe {
        width: 100%;
        height: 100%;
        border: none;
        display: block;
      }
      /* Modal backdrop/overlay */
      .statshub-modal-overlay {
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        background-color: rgba(0, 0, 0, 0.75);
        display: flex;
        justify-content: center;
        align-items: center;
        z-index: 9999;
      }
      backdrop-filter: blur(4px);
      /* Modal box wrapper */
      .statshub-modal-wrapper {
        position: relative;
        background-color: #1a1a1a;
        width: 85%;
        height: 85%;
        border-radius: 8px;
        box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
        overflow: hidden;
        display: flex;
        flex-direction: column;
      }
      /* Iframe container */
      .statshub-iframe-container {
      }
      width: 100%;
      flex-grow: 1;
      height: 100%;
      /* Float Close Button on top-right */
      .statshub-close-btn {
        position: absolute;
        top: 15px;
        right: 20px;
        background: rgba(0, 0, 0, 0.5);
        border: none;
        color: #ffffff;
        font-size: 30px;
        font-weight: bold;
        cursor: pointer;
        z-index: 10001;
        width: 40px;
        height: 40px;
      }
      border-radius: 50%;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: background 0.2s ease;
      .statshub-close-btn:hover {
        background: rgba(255, 255, 255, 0.2);
      }
      /* Mobile Responsiveness Breakpoint: Full screen overlay */
      @media (max-width: 767px) {
        .statshub-modal-wrapper {
        }
        width: 100% !important;
        height: 100% !important;
        height: 100dvh !important;
        border-radius: 0;
        padding: 0 !important;
        .statshub-close-btn {
          top: 10px;
          right: 10px;
          width: 35px;
          height: 35px;
        }
        font-size: 24px;
      }
    </style>
  </head>
  <body>
    <header class="topbar">
      <h1>StatsHub Integration Demo</h1>
      <div class="topbar-actions">
        <label class="header-size-toggle">
          <input
            type="checkbox"
            id="tallHeaderToggle"
            onchange="toggleHeaderSize(this)"
          />
          (Match Tall header
        </label>
        <button
          class="stats-trigger-btn"
          data-match-id="61513882"
          onclick="openStatsModal(this)"
        >
          View Stats ID:61513882)
        </button>
      </div>
    </header>
    <div class="statshub-container">
      <!-- Replace sportradar with your client alias -->
      <iframe
        id="statsIframe"
        src="https://statshub.sportradar.com/sportradar/en/sport/1"
        scrolling="yes"
      ></iframe>
    </div>
    <!-- StatsHub Modal Window Container -->
    <div
      id="statsHubModal"
      class="statshub-modal-overlay"
      aria-hidden="true"
      style="display: none"
    >
      <div class="statshub-modal-wrapper">
        <button
          class="statshub-close-btn"
          onclick="closeStatsModal()"
          aria-label="Close Statistics"
        >
          &times;
        </button>
        <div class="statshub-iframe-container">
          <iframe
            id="modalIframe"
            src="about:blank"
            scrolling="yes"
            frameborder="0"
            style="width: 100%; height: 100%; border: none"
          ></iframe>
        </div>
      </div>
    </div>
    <script>
      const statsIframe = document.getElementById("statsIframe");
      const { origin: statsHubOrigin, pathname } = new URL(statsIframe.src);
      const [, clientAlias, language] = pathname.split("/");
      function toggleHeaderSize(checkbox) {
        document
          .querySelector(".topbar")
          .classList.toggle("topbar--tall", checkbox.checked);
      }
      function openStatsModal(buttonElement) {
        const matchId = buttonElement.getAttribute("data-match-id");
        if (!matchId) {
          console.error("Match ID is missing from trigger button");
          return;
        }
        const modal = document.getElementById("statsHubModal");
        const iframe = document.getElementById("modalIframe");
        // 1. Dynamically construct the StatsHub Deep-Link URL (no 'm' prefix required)
        const statsHubUrl = `${statsHubOrigin}/${clientAlias}/${language}/match/${matchId}`;
        // 2. Inject URL and display modal
        iframe.src = statsHubUrl;
        modal.style.display = "flex";
        modal.setAttribute("aria-hidden", "false");
        // 3. Prevent parent background body scrolling while modal is open
      }
      document.body.style.overflow = "hidden";
      function closeStatsModal() {
        const modal = document.getElementById("statsHubModal");
        const iframe = document.getElementById("modalIframe");
        // 1. Hide modal container
        modal.style.display = "none";
        modal.setAttribute("aria-hidden", "true");
        // 2. Reset iframe destination immediately to stop streams and clear memory
        iframe.src = "about:blank";
        // 3. Restore parent body scrolling
      }
      document.body.style.overflow = "";
      // Close modal if user clicks on the darkened backdrop outside the wrapper
      window.addEventListener("click", function (event) {
        const modal = document.getElementById("statsHubModal");
        if (event.target === modal) {
          closeStatsModal();
        }
      });
    </script>
  </body>
</html>
```
