OnAim Integration Guide
1. Introduction
OnAim is a gamification as a service platform that enables casinos, betting operators, and other digital businesses to create event-driven player promotions — without custom development for each campaign.Your system sends player activity events (such as Bet, Win, Registration, Deposit, or custom-defined events) to OnAim in real time.
OnAim processes these events according to your promotion’s configuration — awarding coins, filling progress bars, updating leaderboards, unlocking prizes, and more.
Players access promotions through a Promotion Landing Page, which you embed into your platform via an iframe, allowing a seamless in-platform experience.
To ensure smooth integration:
- Player identification must be secure and verified.
- Events must include the required fields.
- Specific backend endpoints must be available for player info and coin withdrawals.
Note: OnAim operates as a single-tenant platform. Each partner has a dedicated, isolated environment for event ingestion, data processing, and player interactions.
---
2. Event Integration
OnAim’s event processing system supports multiple integration channels depending on your infrastructure.2.1 Supported Integration Methods
| Method | Description | Recommended Use |
|---|---|---|
| REST API | Send events to OnAim via HTTPS POST requests. | Simpler integrations, low to medium traffic |
| RabbitMQ | Publish events to a configured RabbitMQ exchange. | Asynchronous event-driven systems |
| Kafka (AVRO or JSON) | Send serialized events to a Kafka topic provided by OnAim. | High-volume, distributed systems |
Note: These values are indicative and may vary based on deployment and load. Each tenant can request customized throughput limits.
2.2 Dynamic Event Schema
OnAim accepts fully dynamic event structures.Only the following three fields are mandatory:
{
"customerId": "string",
"eventType": "string",
"timeStamp": "string (ISO 8601, UTC recommended)"
}
All other fields are free and flexible — you can include any additional data relevant to your use case.
These fields can be:
- Shared properties, such as `deviceType`, `promotionId`, `country`, or `sessionId`
- Event-specific properties, such as `amount`, `gameName`, or `RefereeId`
UTC Time: Timestamps should be provided in UTC. Non-UTC values are accepted but can result in misaligned reporting and aggregation windows.
If an event is missing any of the three required fields (`customerId`, `eventType`, `timeStamp`):
- The event will be rejected and logged in the ingestion service.
- Logs will contain the tenant ID, malformed payload, and timestamp for traceability.
There is no restriction on additional data — OnAim automatically flattens all properties for processing. You can extend event structure anytime without redeploying backend code.
2.3 Example Event
{
"customerId": "P_123456",
"eventType": "Bet",
"timeStamp": "2025-05-23T10:16:58.980Z",
"deviceType": "Mobile",
"gameName": "Roulette",
"gameType": "Table",
"amount": 100,
"currency": "USD"
}
- `customerId`, `eventType`, and `timeStamp` are mandatory.
- `deviceType` and `promotionId` are shared fields.
- `gameName`, `gameType`, `amount`, and `currency` are custom properties.
- The structure is flat — all properties are accessible in the Admin Panel once described in the schema.
2.4 Schema Description for Admin Panel
While OnAim accepts any dynamic event, the Admin Panel can only display and filter fields that are defined in your schema definition.This schema can be defined via:
- UI configuration in the OnAim Admin Panel
The schema describes all fields and expected types. Once registered, OnAim automatically generates UI elements (filters, dropdowns, numeric inputs) for building conditions and analytics.
Example Schema Description:{
"customerId": "string",
"eventType": "Bet | Win | Registration | Deposit | CustomEvent",
"timeStamp": "2025-05-23T10:16:58.980Z",
"deviceType": "Mobile | Desktop | Tablet | Other",
"customProperties": {
"Bet": {
"gameName": "string",
"gameType": "string",
"amount": 100,
"currency": "USD"
},
"Win": {
"gameName": "string",
"value": 250,
"coinId": "FreeSpin"
},
"Registration": {
"RefereeId": "string",
"RegistrationDate": "2025-05-23T10:16:58.980Z"
}
}
}
Schema Description Rules
| Rule | Description |
|---|---|
| Required fields | Only customerId, eventType, and timeStamp are mandatory. |
| Shared fields | Common fields (e.g., deviceType, promotionId, country) should be defined at the root level. |
| Event-specific fields | Must be described inside customProperties under their event type key. |
| Data type or example value | Specify either a data type (string, number, date) or example value ("USD", 100). |
| Optional fields | You can omit properties; they’ll still be processed but won’t appear in Admin filters. |
| Extensibility | The schema can be modified or extended anytime without affecting existing data. |
| Flattened structure | All fields (shared + event-specific) become flat for analytics and filtering. |
---
3. Promotion Page Integration (Iframe + OTT Authentication)
Players interact with promotions through a Promotion Landing Page hosted by OnAim. You can embed this page inside your platform using an iframe.3.1 Base URL
https://[onaim-landing].io/?promotionId=752&lang=ka&landingPageId=574
3.2 Secure Player Identification (OTT Token)
Each player is identified using a One-Time Token (OTT) generated by your backend.
https://[onaim-landing].io/?promotionId=752&lang=ka&landingPageId=574&ott=80b6a17cc251
OTT Requirements
| Property | Description |
|---|---|
| Unique per session | Must be generated for one player session only. |
| Server-side generation | Must be created and signed by your backend. |
| Secure transmission | Must be passed over HTTPS only. |
<iframe src="https://[onaim-landing].io/?promotionId=752&lang=ka&landingPageId=574&ott=80b6a17cc251" width="100%" height="800" frameborder="0" allowfullscreen></iframe>
---
4. Player Info Endpoint
The Player Info Endpoint is used by OnAim to fetch information about a player whenever the promotion landing page or event processing requires it.There is no strict requirement to use the OTT-based flow. The endpoint can be implemented in any form that your system supports, as long as OnAim can uniquely identify the player and retrieve accurate player details.
For example:
- You can use the previously described OTT flow for higher security.
- Or, you can expose a direct authenticated endpoint that takes any identifying token, or session key to resolve the player.
The only requirement is that your endpoint must reliably return the correct player information based on the identifier provided by OnAim.
Example Request
GET https://[yourdomain].com/onaim/player?token={ANY_UNIQUE_IDENTIFIER}
Example Response
{
"PlayerId": "P_123456" ,
"UserName": "JohnDoe",
"Email": "[email protected]",
"Country": "GE",
}
4.3 Field Mapping
Map OnAim’s required fields to your backend’s property names and provide the mapping as shown below:{
"FieldMappings": {
"default": {
"PlayerId": "YOUR_PLAYER_ID_PROPERTY",
"UserName": "YOUR_PLAYER_NAME_PROPERTY"
}
}
}
---
5. Coin Withdrawal Endpoint
When players redeem withdrawal coins (coins that represent real or virtual currency), OnAim triggers your withdrawal endpoint to process the payout.All withdrawal options — endpoint URL, HTTP method, headers, query parameters, and body mappings — are fully configured in the OnAim Admin Panel UI.
5.1 Example Configuration
{
"endpointUrl": "https://api.yourdomain.com/onaim/withdraw",
"method": "POST",
"headers": {
"Authorization": "Bearer {API_KEY}"
},
"body": {
"playerId": "{PlayerId}",
"amount": "{Amount}",
"currency": "{Currency}",
"transactionId": "{TransactionId}"
}
}
5.2 Response Requirements & Behavior
| Condition | Expected | Description |
|---|---|---|
| Success | HTTP 200 OK | Withdrawal confirmed successfully. |
| Failure | HTTP 4xx / 5xx | Transaction marked as failed; visible in admin logs. |
| Timeout | N/A | If the request exceeds timeout (default: 5s), OnAim retries up to 3 times with exponential backoff if idempotency is supported. |
| Duplicate prevention | N/A | If your system supports idempotency keys using transactionId, OnAim safely retries without creating duplicates. Otherwise, OnAim will perform only one attempt to avoid double payouts. |
5.3 Retry and Rollback Policy
- If idempotency is implemented on your side (using `transactionId` as a unique key):
- OnAim may safely retry failed or timed-out requests.
- Retries will not create duplicate transactions.
- You may also roll back or reconcile failed attempts safely.
- If no idempotency mechanism exists:
- OnAim will send the withdrawal request once.
- If it fails or times out, the transaction will be marked as failed in Admin.
- Manual review or reprocessing will be required.
Recommendation: Always implement an idempotent withdrawal endpoint that can detect duplicate `transactionId` values. This enables safe automatic retries and prevents payout duplication.
---
6. Security & Best Practices
- Always use HTTPS for all communications.
- Generate OTTs server-side only.
- Limit OTT validity to 1 minute or less.
- Always provide timestamps in UTC for consistent analytics.
- Validate every event payload for the three required fields.
- Use idempotency keys to prevent duplicates in withdrawals and event ingestion.
- Implement acknowledgment and retry mechanisms for message broker integrations.
- Regularly update your schema definition to expose new event fields.
- Consider adding monitoring dashboards to visualize event flow and error rates.
7. Promotion Page Embedding (Frontend)
Sections 1–6 cover the backend integration (events, player info, withdrawals). This section covers the frontend side: how the Promotion Landing Page is embedded into your site and how it communicates with your page at runtime (login, deposit, balance, resize). It expands on the basic iframe embed in Section 3.
There are two embed shapes, both served from the same renderer. The iframe is the currently recommended integration; the Web Component widget is also supported.
| Shape | What it is | When to use |
|---|---|---|
| iframe (recommended) | The landing page loaded as a URL inside an <iframe>. Communicates via window.postMessage. |
Default embed. Broadest host compatibility and cross-origin isolation. |
| Web Component widget | A <onaim-landing-page> custom element loaded from a single JS bundle. Style-isolated (Shadow DOM). Communicates via element methods + DOM events. |
Embedding directly in your own page: no iframe, no scrollbars, sizes to content automatically. |
7.1 iframe embedding (recommended)
Embed the landing page as a URL inside an <iframe>, using the base URL and OTT described in Section 3. At runtime the iframe and your page exchange postMessage events (see 7.3 and 7.4).
<iframe
id="onaim-landing"
src="https://[onaim-landing].io/?promotionId=752&lang=ka&landingPageId=574&ott=80b6a17cc251"
style="width:100%; border:none;">
</iframe>
The iframe posts its content height so you can resize it (7.5). Do not give it a fixed or viewport-relative height in that mode — pass &embedMode=inner in the URL if you want to control the height yourself and disable auto-height posting.
7.2 Web Component widget
Add the widget bundle and drop the element wherever the promotion should appear. OnAim provides the bundle URL and your promotion-id / landing-page-id values — hosting and environment are managed by OnAim, so no additional configuration is required on your side.
<!-- widget bundle (URL provided by OnAim) -->
<script type="module" src="https://<LANDING_HOST>/widget/onaim-landing-page.js"></script>
<!-- the element -->
<onaim-landing-page
promotion-id="752"
landing-page-id="574"
language="ka"
otp="80b6a17cc251">
</onaim-landing-page>
The widget fetches its own configuration, renders inside its Shadow DOM, and grows to fit its content — no height messaging is required.
Widget attributes
| Attribute | Required | Description |
|---|---|---|
promotion-id |
Yes | Promotion/campaign id (integer). |
landing-page-id |
Yes | Landing-page id (integer). |
language |
No | Language code, e.g. en, ka. |
otp |
No | Player One-Time Token for personalized/authenticated content (see Section 3.2). |
enable-signalr |
No | Real-time updates. Enabled unless set to "false". |
config |
No | Inline configuration JSON (an array of components, or { components, fonts }). Testing/preview only — normally the widget fetches its own configuration. |
Methods and events
Call methods directly on the element, and listen for its DOM events:
const el = document.querySelector('onaim-landing-page');
el.setLanguage('en'); // switch language at runtime
// el.setConfig([...]); // replace config (preview/testing only)
el.addEventListener('ready', () => console.log('landing ready'));
el.addEventListener('error', (e) => console.error('landing error', e.detail));
Note: in widget mode, player actions such as login / register / deposit are handled inside your page by the renderer's per-deployment configuration, not by the postMessage codes below. The codes in 7.3 apply to the iframe embed.
7.3 Messages FROM the landing page (iframe → host)
Add a single message listener on the parent window. Command messages carry both a numeric code and a string action (match on either); the message field is a human-readable label and must not be used for control flow.
| code | action | Extra | Meaning / host action |
|---|---|---|---|
1001 |
login |
— | Player requested sign-in. Show your login UI; after a successful login, reload the iframe with a fresh OTT. |
1002 |
register |
— | Player requested registration. Show your sign-up UI. |
1003 |
height |
value (px) |
Content height changed. Resize the iframe (7.5). |
1011 |
openTab |
value (URL) |
Open an external link in a new tab. |
1012 |
deposit |
value (method or null) |
Player triggered a deposit. Open your deposit/cashier flow. |
1031 |
scrollTo |
value (Y px) |
(Legacy, optional) Auto-height embeds: scroll the page so this Y offset aligns to the top of the viewport, to bring a popup into view. |
State messages use type (no code):
| type | Extra | Meaning |
|---|---|---|
balance-update |
amount (number) |
In-game balance changed. Refresh your balance display. |
no-more-balance |
— | Player is out of balance. Prompt a deposit. |
tournament-play |
— | Tournament-over "Play" CTA. Route the player to the next playable surface. |
Native app embeds: inside the OnAim native app, thedeposit(1012) and alink/ redirect command (code 1010, withvalue= URL) are delivered throughwindow.PromotionChannelinstead ofpostMessage. Pure web hosts only need thepostMessagecodes above.
Complete listener:
const iframe = document.getElementById('onaim-landing');
window.addEventListener('message', (event) => {
// Recommended: verify the sender in production.
// if (event.origin !== 'https://<LANDING_HOST>') return;
const { type, code, value, amount } = event.data || {};
switch (code) {
case 1001: showLoginModal(); break; // login
case 1002: showRegisterModal(); break; // register
case 1003: iframe.style.height = value + 'px'; break; // height
case 1011: // openTab
if (typeof value === 'string' && value.startsWith('https:')) {
window.open(value, '_blank', 'noopener');
}
break;
case 1012: openDepositFlow(value); break; // deposit: 'VISA'|'BANK'|'AMEX'|'APPLE'|null
case 1031: window.scrollTo({ top: value, behavior: 'smooth' }); break; // scrollTo (legacy)
}
switch (type) {
case 'balance-update': updateBalance(amount); break;
case 'no-more-balance': showDepositPrompt(); break;
case 'tournament-play': routeToNextGame(); break;
}
});
Security: treat everyvalueURL as untrusted — allow onlyhttps:links and open them withnoopener. In production, validateevent.originagainst your landing host before acting on a message.
7.4 Messages TO the landing page (host → iframe)
Post to the iframe's contentWindow:
const iframe = document.getElementById('onaim-landing');
iframe.contentWindow.postMessage({ type: 'SET_LANGUAGE', payload: 'en' }, '*');
| type | Payload | Effect |
|---|---|---|
SET_LANGUAGE |
string |
Switch the display language. |
SET_LANGUAGES |
string[] |
Set the list of available languages. |
SET_CONFIG |
config object/array | Replace the rendered component configuration. |
SET_GLOBAL_VARIABLES |
object (CSS variables) | ⚠️ Not currently applied in iframe mode. For widget theming, use the element's setGlobalVariables() method instead. |
7.5 Height auto-adjustment (iframe)
Unless you pass embedMode=inner, the iframe measures its content and posts a height command (code 1003) whenever it changes (initial load, dynamic content, layout shifts). Apply it so there is no inner scrollbar:
window.addEventListener('message', (event) => {
const { code, value } = event.data || {};
if (code === 1003) {
document.getElementById('onaim-landing').style.height = value + 'px';
}
});
A CSS transition: height 0.2s on the iframe keeps resizes smooth; debouncing on your side is not required.
Do not give the iframe a viewport-relative height (100vh,%) in auto-height mode: the content grows with the iframe, which grows with the content — an unbounded loop. Let the posted height drive the size, or useembedMode=innerand size it yourself.
7.6 iframe vs widget
| Concern | iframe (recommended) | Web Component widget |
|---|---|---|
| Embed | <iframe src> |
<onaim-landing-page> + bundle |
| Style isolation | full document boundary | Shadow DOM |
| Host → landing | postMessage (SET_LANGUAGE, SET_CONFIG) |
element methods (setLanguage, setConfig) |
| Landing → host | postMessage codes (7.3) |
DOM events (ready, error) |
| Height | height 1003 → you resize (7.5) |
grows automatically |
| Targeting | promotionId + landingPageId (URL) |
promotion-id + landing-page-id (attributes) |
| Player token | ott URL parameter |
otp attribute |
| Theming | not applied via postMessage |
setGlobalVariables() method |
| Player actions (login / register / deposit) | postMessage codes (7.3) |
per-deployment client scripts |
7.7 Sharing your scroll position with the iframe (host viewport, code 1020)
Recommended for the default auto-height iframe embed. By posting your page's scroll position and viewport size down to the iframe, you let the landing page see where the visible screen is. The landing uses this to place anything that should track the visible viewport rather than the document, including:
- Centered dialogs / modals — open in the middle of the player's screen instead of the middle of the (tall) landing page.
- Pinned / sticky components — stay fixed to the top or bottom of the screen while the player scrolls.
- Any other viewport-relative overlay the landing renders.
Why it's needed: in auto-height mode the iframe is grown to its full content height (7.5), so the iframe itself never scrolls — your page does. Code inside the iframe cannot read your scroll position across origins, so on its own it can only position things relative to the whole (tall) document, not the player's visible screen. The host-viewport message tells it where that visible screen is.
This is optional and safe to skip: without it nothing breaks — dialogs and pinned components fall back to document-relative positioning (a dialog may sit off-screen on a long page; a pinned component scrolls with the page). Implement it to get correct viewport-relative placement in the auto-height embed. The Web Component widget and self-sized iframes (embedMode=inner) already have a real viewport, so they don't need it.
Post your viewport to the iframe
Send a host-viewport message to the iframe's contentWindow on load, on scroll, and on resize:
| Field | Type | Value |
|---|---|---|
action |
string |
"host-viewport" |
code |
number |
1020 |
scrollY |
number |
Current vertical scroll position of your page, in px (window.scrollY). |
innerHeight |
number |
Height of the visible viewport, in px (window.innerHeight). |
iframeTop |
number |
The iframe element's top edge in your document: rect.top + window.scrollY. |
All three numeric fields are required and must be finite numbers — a malformed message is ignored and the last good value is kept.
const iframe = document.getElementById('onaim-landing');
function postHostViewport() {
const rect = iframe.getBoundingClientRect();
iframe.contentWindow.postMessage({
action: 'host-viewport',
code: 1020,
scrollY: window.scrollY,
innerHeight: window.innerHeight,
iframeTop: rect.top + window.scrollY, // iframe's top in the document
}, '*');
}
// Post on scroll and resize (passive listeners keep scrolling smooth)…
window.addEventListener('scroll', postHostViewport, { passive: true });
window.addEventListener('resize', postHostViewport);
// …and once when the iframe is ready, so overlays are placed correctly before any scroll.
iframe.addEventListener('load', postHostViewport);
// Optional: the iframe may ask for the current viewport (action
// 'host-viewport-request', code 1021), e.g. when a dialog opens or a pinned
// component mounts. Answer by posting once.
window.addEventListener('message', (event) => {
if (event.source === iframe.contentWindow && event.data?.code === 1021) {
postHostViewport();
}
});
Tip: you can reuse this for any cross-origin overlay alignment, not just pinned components — it tells the landing where your visible screen is. Keep posting on every scroll/resize; the landing throttles the updates to one per animation frame.