Iframe Events
Bi-directional postMessage communication between the game widget and your platform
When embedding the 155.io game widget in an iframe, you can communicate with it using the browser's postMessage API. This allows you to:
- Send commands to the game (e.g., refresh balance, mute audio)
- Listen for events from the game (e.g., bet placed, balance insufficient, round lifecycle)
Message Format
All messages use a versioned envelope format:
interface PostMessageEnvelope {
source: 'marbles-game-widget' | 'marbles-operator'
type: string
version: 1
payload: Record<string, unknown>
timestamp: number
requestId?: string // optional correlation ID
}The source field identifies the sender. Messages from the game widget use marbles-game-widget, messages you send must use marbles-operator.
Sending Commands
To send a command to the game widget, use postMessage on the iframe element:
function sendCommand(iframe: HTMLIFrameElement, type: string, payload: Record<string, unknown> = {}) {
iframe.contentWindow?.postMessage({
source: 'marbles-operator',
type,
version: 1,
payload,
timestamp: Date.now()
}, '*')
}
// Example: refresh balance
const iframe = document.getElementById('game-iframe') as HTMLIFrameElement
sendCommand(iframe, 'refresh-balance')function sendCommand(iframe, type, payload = {}) {
iframe.contentWindow?.postMessage({
source: 'marbles-operator',
type,
version: 1,
payload,
timestamp: Date.now()
}, '*')
}
// Example: refresh balance
const iframe = document.getElementById('game-iframe')
sendCommand(iframe, 'refresh-balance')Available Commands
| Command | Payload | Description |
|---|---|---|
refresh-balance | {} | Force the game to re-fetch the player's balance from your server |
mute-audio | { muted: boolean } | Mute or unmute all game audio |
set-audio-volume | { volume: number } | Set master volume (0.0 to 1.0) |
open-game-history | {} | Open the bet history sidebar |
close-panels | {} | Close all open panels and sidebars |
get-state | {} | Request a snapshot of the current game state (responds with state-snapshot event) |
refresh-balance is debounced to a maximum of once every 2 seconds to prevent excessive server requests. All commands are rate-limited to 30 per second per command type.
Listening for Events
To receive events from the game widget, add a message event listener:
interface GameEvent {
source: 'marbles-game-widget'
type: string
version: 1
payload: Record<string, unknown>
timestamp: number
requestId?: string
}
window.addEventListener('message', (event: MessageEvent) => {
const data = event.data as GameEvent
// Only handle messages from the game widget
if (data.source !== 'marbles-game-widget') return
switch (data.type) {
case 'balance:insufficient':
// Player tried to bet but doesn't have enough balance
// Show a deposit prompt in your UI
console.log('Insufficient balance:', data.payload)
break
case 'bet:placed':
console.log('Bet placed:', data.payload)
break
case 'bet:result':
console.log('Bet result:', data.payload)
break
case 'balance:changed':
console.log('Balance updated:', data.payload)
break
}
})window.addEventListener('message', (event) => {
const data = event.data
// Only handle messages from the game widget
if (data.source !== 'marbles-game-widget') return
switch (data.type) {
case 'balance:insufficient':
console.log('Insufficient balance:', data.payload)
break
case 'bet:placed':
console.log('Bet placed:', data.payload)
break
case 'bet:result':
console.log('Bet result:', data.payload)
break
case 'balance:changed':
console.log('Balance updated:', data.payload)
break
}
})Game Lifecycle Events
| Event | Payload | When |
|---|---|---|
game:loaded | { gameId, gameType, playerId } | Player authenticated and game widget is ready |
game:ready | { gameId, roundId } | First round is available for betting |
game:error | { code, message } | Fatal error occurred (e.g., connection failure, authentication error) |
session:expired | {} | Player session has expired |
Round Lifecycle Events
| Event | Payload | When |
|---|---|---|
round:betting-open | { roundId, gameId } | Betting window is open |
round:betting-closed | { roundId, gameId } | Betting window has closed, race starting soon |
round:started | { roundId, gameId } | Race animation has begun |
round:ended | { roundId, gameId, crashMultiplier? } | Race is finished. crashMultiplier is included for crash games |
Bet Events
| Event | Payload | When |
|---|---|---|
bet:placed | { betId, amount, currency, betType } | Bet was accepted by the server |
bet:rejected | { reason, amount, currency } | Bet was rejected (see Error Codes) |
bet:result | { betId, status, payout?, currency, multiplier? } | Bet outcome determined |
cashout:completed | { betId, multiplier, payout, currency } | Player cashed out during a crash game |
The bet:result status field will be one of: won, lost, cashed_out, or voided.
Balance Events
| Event | Payload | When |
|---|---|---|
balance:changed | { amount, currency } | Player balance has been updated |
balance:insufficient | { requiredAmount?, currentBalance, currency } | Player tried to bet but doesn't have enough balance |
Deposit Prompts
Listen for balance:insufficient to show a deposit dialog or redirect to your deposit page — this is the primary use case most operators integrate first.
Player State Events
| Event | Payload | When |
|---|---|---|
player:idle-warning | { gameId } | Player has been idle — warning shown |
player:idle-paused | { gameId } | Player idle too long — game stream paused |
state-snapshot | { isLoaded, playerId?, gameId?, balance?, isMuted, hasActiveBet } | Response to get-state command |
Full Integration Example
Here's a complete example showing how to embed the game and handle events.
Use the game URL returned by the Get Game URL endpoint as the iframe src.
<iframe
id="game-iframe"
src="YOUR_GAME_SESSION_URL"
style="width: 100%; height: 600px; border: none;"
allow="autoplay"
></iframe>
<script>
const iframe = document.getElementById('game-iframe')
function sendCommand(type, payload = {}) {
iframe.contentWindow?.postMessage({
source: 'marbles-operator',
type,
version: 1,
payload,
timestamp: Date.now()
}, '*')
}
window.addEventListener('message', (event) => {
if (event.data?.source !== 'marbles-game-widget') return
const { type, payload } = event.data
switch (type) {
case 'game:loaded':
console.log('Game ready for player:', payload.playerId)
break
case 'balance:insufficient':
// Show your deposit UI
showDepositDialog(payload.currency)
break
case 'bet:placed':
console.log(`Bet ${payload.betId}: ${payload.amount} ${payload.currency}`)
break
case 'bet:result':
if (payload.status === 'won') {
console.log(`Won ${payload.payout} ${payload.currency}!`)
}
break
case 'round:betting-open':
console.log('Betting is open for round:', payload.roundId)
break
case 'session:expired':
// Re-authenticate the player
refreshGameSession()
break
}
})
// Example: refresh balance after an external deposit
document.getElementById('deposit-done').addEventListener('click', () => {
sendCommand('refresh-balance')
})
// Example: mute the game
document.getElementById('mute-btn').addEventListener('click', () => {
sendCommand('mute-audio', { muted: true })
})
</script>Notes
- All
amountandpayoutvalues are in the player's currency as decimal numbers (e.g.,5.00for $5 USD) - The game widget works without postMessage — these events are optional enhancements
- Messages with an unrecognized
typeare silently ignored, ensuring forward compatibility as new events are added - The
versionfield allows for future protocol changes without breaking existing integrations