Oddin.gg Thor - Affiliate widgets documentation
Introduction
Oddin.gg’s Affiliate marketing widgets, Thor, are a convenient, plug-and-play solution that enables partners to monetize the popularity of their platforms within the esports community by channeling traffic towards betting operators interested in engaging with their user base.
We currently support the Player’s upcoming matches widget, available at the /player endpoint. This provides end users with a list of future matches across all tournaments for professional players, alongside up-to-date betting opportunities on a variety of betting platforms, for each of these matches.
This document will walk you through the steps required to integrate Thor on your website.
1. iFrame integration
Widgets behave as iFrame elements within HTML. We created a REST API that will prepare URL addresses for each widget - the returned URL address is used as src in the iFrame.
| Item | Description |
| REST API URL | https://api-afl.fulla.bet/<env>/afl |
<env> takes the values:
integration- for the Integration environmentmain- for the Production environment.
1.1. Usage
Call the REST API from your backend to receive the URL address of the widget. The address from the API response (JSON format) should then be used as src in the iFrame element. Subscribe to the iFrame events to resize the iFrame and eliminate the scrollbar in the widget.
ℹ In case the API request to the /player endpoint is valid, it will always return an iframe URL, even if the player does not exist or the geography is not supported. The URL can be plugged into the iframe tag on your website, and this tag set by default to height 0 (hidden). The iFrames are reporting events via the Window.postMessage() method, so, there can be a few scenarios for the RESIZE event:
- A non-existent player was added to our database, therefore, we generate a widget → you will receive a RESIZE event to a size > 0.
- A previously disallowed geography is now possible → there will be again a RESIZE event to a size > 0.
- If the list of matches increases or decreases, the widget will require more or less vertical space, hence, again a RESIZE event.
- The opposite can also happen: a player has no upcoming matches, for example, after he/she plays a tournament or retires → RESIZE to 0 will occur (no widget).
1.2. Domain whitelisting
Every domain where the Widgets will be running needs to be whitelisted in the Oddin.gg system, including the domains used for staging/integration purposes. At the start of the Widgets integration process, you should list all such domain URLs and send them to Oddin.gg integration support.
2. REST API
This chapter provides a complete reference to the REST API. Here you will find the description of all methods and other necessary information required to interact with the REST API.
Additional parameters:
- All responses use JSON format.
- All requests are without body.
2.1. Authentication
The REST API is secured via a brand token. You should receive your brand token from our support. If the token is not working, or you still need to obtain it, please contact us at support@oddin.gg. Please note that you need a different access token for each environment, and then you need to supply the access token in each request in the header - “x-brand-token”.
2.2. Available endpoints
The REST API offers the following endpoints:
| HTTP Method | Path | Description |
| GET | /player/<player_id> |
Returns the URL for the player widget; <player_id> needs to be in the URN format obtained from the Odds Feed API, for example: od:player:1234, or from external source ext:player:s1mple, where s1mple is the player ID from you as an external source. |
We currently provide two different environments - Production and Integration. The Production environment is intended to be used only when you are finished with integration and ready to go live. Please use the Integration environment for all development and testing purposes. You can select the environment by providing a different parameter in the URL for the REST calls.
2.2.1. Player endpoint
2.2.1.1. Query parameters
| Name | Required | Type | Default | Description |
| user_country | yes | string | ISO 3166-1 Alpha-2 country codes. Ex: The United States of America is ‘US‘. Required parameter; if not available, user_ip must be provided instead. | |
| user_ip | no | string | User IPv4. | |
| theme | no | string | “dark” | Switch between the light, dark, or custom theme. The dark theme is used by default. Supported values: “light” | “dark” | “<custom theme name>” |
2.2.1.2. Response body
| Name | Required | Type | Description |
| url | yes | string | URL used as src in the iFrame element. |
2.3. Error response
The HTTP response code will be the same as in the JSON body.
| Name | Required | Type | Description |
| code | yes | integer | HTTP response code. |
| message | no | string | Additional information about the error. |
We use the standard HTTP response codes. Please check the next table for a description of statuses used:
| Code | Name | Description |
| 200 | OK | The request was successful. |
| 401 | Unauthorized | The brand token is missing or invalid, or has expired. |
| 404 | Not found | The player is not available, or the country is not supported. |
| 405 | Invalid arguments | Invalid arguments in the REST call. |
| 500 | Internal server error | Unable to process the request. |
3. Events
iFrame widgets are reporting events via the Window.postMessage() method.
3.1. Subscribe to events
You should listen to the message events on the window - the iFrame is sending serialized JSON objects with a type property.
TypeScript code example
function subscribeToEvents(
iframe: HTMLIFrameElement,
onMessage: (event: WidgetEvent) => void,
onError?: (err: Error) => void
) {
const handleMessage = (e: MessageEvent) => {
if (e.source !== iframe.contentWindow) {
return
}
try {
const message = JSON.parse(e.data)
if (message && typeof message.type === 'string') {
onMessage(message)
}
} catch (e) {
onError?.(e)
}
}
window.addEventListener('message', handleMessage)
return () => window.removeEventListener('message', handleMessage)
}
3.2. Available events
type EventLoaded = {
type: 'LOADED'
}
type EventResize = {
type: 'RESIZE'
height: number
}
type WidgetEvent = EventLoaded | EventResize
| Type | Description |
| LOADED | Widget loaded. |
| RESIZE | The “resize widget” request. The iFrame height should be updated to match the widget height. |
Events
3.3. Example use
const unsubscribe = subscribeToEvents(
iframeInstance,
(event) => {
switch (event.type) {
case 'RESIZE':
iframeInstance.style.height = `${event.height}px`
break
case 'LOADED':
setLoading(false)
break
}
},
(err) => {
// Error
}
)