Skip to content

Integration

The betting frame allows seamless integration into partner websites, providing a complete betting experience with customization options for theming, localization, and event handling. The integration can be done either as a Single Page Application (SPA) or with Server-Side Rendering (SSR), depending on your needs.

Configuration options

AppInitOptions

AppInitOptions is the top-level configuration object, which has the following structure:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
interface AppInitOptions {
  token: string;
  locale: string;
  currency: string;
  rootElement: string;
  isAuthorized: boolean;
  url: BettingUrl;
  features: Record<FeatureKeys, boolean>;
  theme: Theme;
  defaultSettings: DefaultSettings;
}
Property Description Type Required
token Token of the current player Token string yes
locale Selected language Locale code yes
currency Currency code in the ISO:4217 format string yes
rootElement Identifier of the DOM element in which betting will be embedded string yes
isAuthorized Player's authorization status boolean yes
url All endpoints for betting BettingUrl yes
features A set of Features that are responsible for connecting various SPA functionality. Record<FeatureKeys, boolean> no
defaultSettings Default settings for the SPA. DefaultSettings yes

BettingURL contains all the necessary endpoints for the SPA:

1
2
3
4
5
interface BettingUrl {
  gqlEndpoint: string;
  staticEndpoint: string;
  basename: string;
}
Property Description Type Required
gqlEndpoint Path to the GraphQL server string yes
staticEndpoint Path to the server where all the statistics are stored (js/css/assets) string yes
basename The basename field is used to specify the base URL for all relative links within the embedded betting. This is particularly useful when the application is deployed within a subdirectory of a domain. string yes

Features

The SPA provides an option for enabling or disabling various new, or experimental features:

  • IS_MEMORY_ROUTER uses MemoryRouter instead of BrowserRouter, allowing internal navigation within the SPA without changing the site's URL

Default settings

DefaultSetting options are used to set the initial configuration of the application.
They define default values for various settings, such as display odds, which can later be changed by gambler.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
interface DefaultSettings {
  oddFormat: OddFormat;
  oddAcceptStrategy: OddAcceptStrategy;
}

type OddFormat =
  | "Decimal"
  | "Fractional"
  | "US"
  | "HongKong"
  | "Indo"
  | "Malay";

type OddAcceptStrategy = "acceptAll" | "acceptHigher" | "alwaysAsk";
Property Description Type Default value Required
oddFormat Default odd format for the SPA. OddFormat Decimal false
oddAcceptStrategy Default odd accept strategy for the SPA. OddAcceptStrategy acceptAll false

SPA Integration

To integrate the betting SPA into your product, follow these steps:

1. Add DOM containers

You must include the following elements in your HTML layout:

  • Main container — this is where the entire betting application will be rendered:
    1
    <div id="betting__container"></div>
    
  • Betslip containers — separate element for betslip views:
1
<div id="betting-betslip"></div>

Note

These elements must exist in the DOM before initializing the SPA

2. Add the bootstrap script

Include the script for the appropriate environment. Use the staging version during development and testing, and production for live deployment.

  • Production:
    1
    2
    3
    4
    5
    <script
      type="module"
      defer
      src="https://spa.databet.cloud/v2/<project-name>/bootstrap.js"
    ></script>
    
  • Staging:
    1
    2
    3
    4
    5
    <script
      type="module"
      defer
      src="https://spa.int.databet.cloud/v2/<project-name>/bootstrap.js"
    ></script>
    

Warning

type="module" is required attribute

Note

This links will be provided by DATA.BET

3. Initialize the application

Once the script has loaded, a global object bettingLoader becomes available on the window. Use its load method to start the SPA:

1
2
3
4
5
6
interface BettingLoader {
  load: (
    options: AppInitOptions,
    onLoad: (bettingAPI: BettingAPI) => void
  ) => void;
}

Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
<div id="betting__container"></div>
<div id="betting-betslip"></div>

<script
  type="module"
  async
  src="https://spa.databet.cloud/v2/<project-name>/bootstrap.js"
  onload="loadBetting()"
></script>
<script>
  function loadBetting() {
    bettingLoader.load(
      {
        url: {
          staticEndpoint: `<link to static>`, // will be provided by DATA.BET
          gqlEndpoint: "<link to api endpoint>", // will be provided by DATA.BET
          basename: "/",
        },
        token: `<token>`,
        locale: "uk",
        currency: "usd",
        rootElement: "betting__container",
        isAuthorized: true,
        theme: {
          palette: {
            colorsPrimary1: "#000000",
            colorsPrimary2: "#ffffff",
            colorsSecondary1: "#f3f3f3",
            colorsSecondary2: "#e3e3e3",
            colorsSecondary3: "#9999a1",
            colorsAccent1: "#ff6b00",
            colorsAccent2: "#ffb700",
            colorsAccent3: "#a900d9",
            textButton: "#ffffff",
            textPrimary: "#000000",
            textSecondary: "#656565",
            notificationBlocked: "#b5b5b5",
            notificationError: "#ff3e33",
            notificationInfo: "#0852ff",
            notificationSuccess: "#05BC0B",
            notificationWarning: "#ff9000",
          },
        },
        defaultSettings: {
          oddFormat: "Decimal",
        },
      },
      (bettingAPI) => {
        // use bettingAPI to interact with spa on runtime
      }
    );
  }
</script>

Important

Changes to the token, locale, or currency require a page reload to take effect. This limitation is expected to be resolved in future updates.

4.Style the betslip containers

The SPA renders betslip view into dedicated container, depending on screen size:

React Example

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
<script>

  type IStypeBetslip = Pick<ToggleWidgetBetslipPayload, "breakpoint" | "isOpen">;

  const BetslipStyleGetter: Record<
    "static" | "island",
    (data: IStypeBetslip) => string
  > = {
    static: ({ isOpen, breakpoint }: IStypeBetslip): string => {
      if (breakpoint === "mobile") {
        if (isOpen) {
          document.body.classList.add("overflow-hidden");
          return "fixed bottom-0 left-0 top-0 z-40 lg:hidden w-full h-full";
        } else {
          document.body.classList.remove("overflow-hidden");

          return "fixed bottom-0 left-0 -z-40 w-full";
        }
      }

      if (breakpoint === "tablet") {
        return "fixed top-0 left-auto right-0 z-40 h-[100vh]";
      }

      return "";
    },
    island: ({ isOpen, breakpoint }): string => {
      if (breakpoint === "mobile") {
        if (isOpen) {
          document.body.classList.add("overflow-hidden");
          return "fixed bottom-0 left-0 top-0 z-40 lg:hidden w-full h-full max-h-full";
        } else {
          document.body.classList.remove("overflow-hidden");

          return "fixed bottom-0 left-0 z-40 w-full";
        }
      }
      if (breakpoint === "tablet") {
        return "fixed inset-x-1/2 bottom-0 z-[999999] w-full max-w-[320px] max-h-[100vh] -translate-x-1/2";
      }
      return "fixed inset-x-1/2 bottom-0 z-[999999] w-full max-w-[320px] max-h-[80vh] -translate-x-1/2";
    },
  };

  const BetslipContainer: FC = () => {
    const [state, setState] = useState<ToggleWidgetBetslipPayload | undefined>();

    const initBetting = useCallback(() => {
      if (!window.bettingAPI) return;

      window.bettingAPI.subscribe("toggle-widget-betslip", setState);
    }, []);

    useEffect(() => {
      document.addEventListener("betting-init", initBetting);

      return () => document.removeEventListener("betting-init", initBetting);
    }, [initBetting]);

    const getter = state
      ? BetslipStyleGetter[state.widgetType] || BetslipStyleGetter["static"]
      : undefined;

    const className = state && getter
      ? getter({ isOpen: state.isOpen, breakpoint: state.breakpoint })
      : "";

    return <div id="betting-betslip" className={className}></div>;
  };
</script>

Again, feel free to adapt this styling for your layout — e.g., adjusting height or position to avoid overlapping your app’s header or sidebar.

SSR Integration

The SSR (Server-Side Rendering) integration with the DATA.BET platform consists of two parts:

  1. Server-side integration — generating betting HTML fragments on the server.
  2. Client-side integration — initializing interactivity via the bettingAPI.

1. Server-side integration

DATA.BET provides an SSR server that returns pre-rendered betting application HTML for a specific user route.
To integrate it, send a POST request to the SSR endpoint. The request URL must exactly match the path and query parameters the user is currently visiting (i.e., pathname + query).

The server endpoint depends on the environment:

  • Production: https://ssr.databet.cloud
  • Staging/Integration: https://ssr.int.databet.cloud

Below is an example used on the demo site (Next.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { headers } from "next/headers";
import { userAgent } from "next/server";

const BettingPage = async ({
  params,
}: {
  params: { locale: string };
}) => {
  const locale = params.locale;

  const requestHeaders = headers();
  const { device } = userAgent({ headers: requestHeaders });
  let requestUrl;

  try {
    requestUrl = new URL(currentHeaders.get("x-url") || "");
  } catch {
    return null;
  }

  const appInitOptions = {
    isAuthorized: true, // Determine based on user status
    rootElement: "root",
    currency: "USD",
    locale: locale,
    defaultLayout: device.type || "desktop",
    token,
    url: {
      gqlEndpoint: process.env.NEXT_PUBLIC_GQL_ENDPOINT || "<your_gql_endpoint>",
      staticEndpoint: process.env.CDN_URL || "<your_static_endpoint>",
      basename: `${locale}/${userTokenLabel}/ssr`,
    },
     theme: { ... }
  };

  // Insert a link to the SSR server based on the current environment.
  const response = await fetch(`https://ssr.int.databet.cloud/${requestUrl.patchname}${requestUrl.search}`, {
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
    },
    method: "POST",
    body: JSON.stringify({
      bettingOptions: appInitOptions,
    }),
  });

  const bettingHtml = await response.text();

  return (
    <>
      <main
        suppressHydrationWarning={true} // Important for SSR content that might differ slightly on hydration
        dangerouslySetInnerHTML={{ __html: bettingHtml }}
      />

      <BettingIntegration />
    </>
  );

⚠️ Important Notes

1. Betting Token Management

The token field in AppInitOptions is required.
You are fully responsible for the management of this token in your application — including:

  • Generation (e.g., via guest or login flow)
  • Validation (e.g., signature or expiration)
  • Storage (e.g., HTTP-only cookie or server session)
  • Refresh or renewal (if applicable)

2. Device Layout Detection

The layout field in AppInitOptions defines how the betting UI should render: 'mobile', 'tablet', or 'desktop'.
This should be determined based on the user's device, typically by inspecting the User-Agent header In a Next.js App Router environment, use the userAgent utility to extract device info safely on the server:

1
2
3
4
5
import { userAgent } from "next/server";
import { headers } from "next/headers";

const requestHeaders = await headers();
const layout = userAgent({ header: requestHeaders }).device.type || "desktop";

3. Request url

The SSR request must target the exact URL that corresponds to the user’s current location, including both the pathname and search components. This ensures that the returned betting content is accurate and relevant for the current route.

In a Next.js application, the current URL should be captured and forwarded via custom headers (e.g., x-url) using middleware. This allows the server component to reconstruct the user's location and perform the SSR request accordingly.

1
2
3
4
5
export function middleware(request: NextRequest) {
  const headers = new Headers(request.headers);
  headers.set("x-url", request.url);
  return NextResponse.next({ headers });
}

2. Client-side integration

Once the server-rendered HTML is loaded and hydrated on the client-side, the betting-init event is dispatched, signaling that window.bettingAPI is ready.

This API enables interaction with the embedded betting application: event subscriptions, register callback, etc.

Example (Client Component in Next.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
"use client";

import { useCallback, useEffect, useState } from "react";

export const BettingIntegration = () => {
  const initBetting = useCallback(() => {
    window.bettingAPI.subscribe(
      "toggle-widget-betslip",
      ({ isOpen: isOpenState }: { isOpen: boolean }) => {
       ...
      }
    );

    // Add additional subscriptions or actions with the BettingAPI if required
  }, []);

  useEffect(() => {
    // Check if bettingAPI is already available (in case betting-init fired earlier)
    if (window.bettingAPI) {
      initBetting();
    } else {
      document.addEventListener("betting-init", initBetting);
    }

    return () => {
      document.removeEventListener("betting-init", initBetting);
    };
  }, [initBetting]);

  return null;
};

3. Fallback to SPA integration if SSR fails

If you are using SSR (Server-Side Rendering) integration and the SSR server responds with an error (for example, a 4xx/5xx HTTP status or a network failure), it is recommended to gracefully fall back to the SPA integration. This ensures that users can still access the betting application even if SSR is temporarily unavailable.

Steps to implement fallback:

  1. Attempt SSR first:
    Try to fetch the SSR-rendered HTML from your SSR endpoint as usual.

  2. Detect SSR failure:
    If the SSR request fails (e.g., non-200 response, network error, or invalid HTML), proceed to load the SPA client-side.

  3. Initialize SPA via bettingLoader:
    Use the same bettingLoader.load method as described above to bootstrap the SPA in the client browser.

Custom Widget

The Betting Frame supports custom widgets, allowing you to integrate your own content into the betting interface. Custom widgets subscribe to mount events from the betting API, enabling you to render custom content in designated containers using any framework or vanilla JavaScript.

Implementation

Custom widgets require:

  1. Event Subscription: Subscribe to widget:сustom_widget:mount events from the betting API
  2. DOM Manipulation: Use the provided DOM element to render custom content
  3. Event Handling: Handle mount events to get container information

Example (React)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
"use client";

import { useBettingApi } from "@/hooks/useBettingApi";
import { useEffect, useCallback, useState } from "react";
import { createPortal } from "react-dom";

interface ICustomWidgetMountEvent {
  containerId: string;
  element: HTMLElement;
}

const CustomWidget = () => {
  const bettingApi = useBettingApi();
  const [portalData, setPortalData] = useState<ICustomWidgetMountEvent | null>(null);

  const handleCustomWidgetMount = useCallback(
    (data: ICustomWidgetMountEvent) => {
      setPortalData(data);
    },
    [],
  );

  useEffect(() => {
    if (!bettingApi) return;

    (bettingApi as any).subscribe(
      "widget:сustom_widget:mount",
      handleCustomWidgetMount,
    );
  }, [bettingApi, handleCustomWidgetMount]);

  if (!portalData) return null;

  const content = (
    // Your react component
  );

  return createPortal(content, portalData.element);
};

export default CustomWidget;

Note

This example shows a React implementation using createPortal. You can use any framework (Vue, Angular, vanilla JavaScript) to manipulate the DOM element provided in the event. The key is to use the element property from the event to render your custom content.

For more details about betting events, see Betting Events.