155API

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

CommandPayloadDescription
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

EventPayloadWhen
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

EventPayloadWhen
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

EventPayloadWhen
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

EventPayloadWhen
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

EventPayloadWhen
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 amount and payout values are in the player's currency as decimal numbers (e.g., 5.00 for $5 USD)
  • The game widget works without postMessage — these events are optional enhancements
  • Messages with an unrecognized type are silently ignored, ensuring forward compatibility as new events are added
  • The version field allows for future protocol changes without breaking existing integrations

On this page