Skip to main content

Events & SQS Integration

The Tournament Platform provides real-time tournament event notifications to keep your casino system synchronized with tournament events. Events are delivered via AWS SQS transport, configured at the infrastructure level rather than through API endpoints.

Event Overview

SQS Event Delivery Architecture

SQS Transport Features

  • Reliable Delivery: AWS SQS guarantees with configurable retry policies
  • Event Ordering: FIFO queues ensure sequential delivery per tournament
  • Dead Letter Queues: Automatic handling of failed message processing
  • Scalable Processing: Handle high-volume event streams efficiently
  • Infrastructure-level Configuration: No API management required

SQS Configuration

SQS transport is configured at the infrastructure level rather than through API endpoints. Contact [email protected] for queue setup and configuration.

Queue Setup Requirements

Tournament Event Queue:
  • Queue Type: FIFO (First-In-First-Out) for ordered event processing
  • Message Retention: 14 days (AWS SQS maximum)
  • Visibility Timeout: 30 seconds (configurable based on processing needs)
  • Dead Letter Queue: Configured for failed message handling
  • Content-Based Deduplication: Enabled for automatic duplicate prevention
Permissions Required:
  • sqs:ReceiveMessage - Consume tournament events
  • sqs:DeleteMessage - Acknowledge processed events
  • sqs:GetQueueAttributes - Monitor queue health

Event Types

Tournament Lifecycle Events

Tournament Started

Sent when a tournament begins with actual players:
{
  "eventType": "tournament.started",
  "eventId": "evt_1642089720_abc123",
  "timestamp": "2024-01-15T20:02:00Z",
  "tournamentId": "12345",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "tournamentName": "Daily Championship",
    "gameSlug": "crash-classic",
    "actualStartTime": "2024-01-15T20:02:00Z",
    "initialPlayerCount": 87,
    "maxPlayers": 100,
    "estimatedDuration": 4500,
    "prizePool": {
      "totalAmount": 870.00,
      "currency": "USD",
      "guaranteedPrize": 500.00
    },
    "gameConfig": {
      "maxRounds": 5,
      "eliminationRate": 0.5,
      "rebuyAllowed": true
    }
  }
}

Tournament Completed

Sent when a tournament finishes with final results:
{
  "eventType": "tournament.completed",
  "eventId": "evt_1642093320_def456",
  "timestamp": "2024-01-15T21:15:20Z",
  "tournamentId": "12345",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "tournamentName": "Daily Championship",
    "completedAt": "2024-01-15T21:15:20Z",
    "duration": 4400,
    "totalPlayers": 87,
    "finalPlayerCount": 87,
    "totalRounds": 5,
    "prizePool": {
      "totalAmount": 870.00,
      "distributed": 870.00,
      "currency": "USD"
    },
    "winners": [
      {
        "position": 1,
        "playerId": "tp_player_456",
        "displayName": "ChampionPlayer",
        "finalScore": 2150,
        "prizeAmount": 435.00,
        "entryCount": 2,
        "totalPaid": 20.00,
        "netWinnings": 415.00
      },
      {
        "position": 2,
        "playerId": "tp_player_789",
        "displayName": "SecondPlace",
        "finalScore": 1980,
        "prizeAmount": 261.00,
        "entryCount": 1,
        "totalPaid": 10.00,
        "netWinnings": 251.00
      },
      {
        "position": 3,
        "playerId": "tp_player_321",
        "displayName": "ThirdPlace",
        "finalScore": 1750,
        "prizeAmount": 174.00,
        "entryCount": 1,
        "totalPaid": 10.00,
        "netWinnings": 164.00
      }
    ],
    "statistics": {
      "averageScore": 1245.5,
      "totalEntryFees": 870.00,
      "totalRebuys": 23,
      "rebuyRevenue": 230.00,
      "operatorRevenue": 110.00
    }
  }
}

Tournament Cancelled

Sent when a tournament is cancelled before completion:
{
  "eventType": "tournament.cancelled",
  "eventId": "evt_1642091000_ghi789",
  "timestamp": "2024-01-15T20:30:00Z",
  "tournamentId": "12345",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "tournamentName": "Daily Championship",
    "cancelledAt": "2024-01-15T20:30:00Z",
    "reason": "insufficient_players",
    "playerCount": 3,
    "minRequiredPlayers": 5,
    "refunds": {
      "totalRefunded": 30.00,
      "playersRefunded": 3,
      "refundMethod": "original_payment"
    }
  }
}

Player Events

Player Registered

Sent when a player successfully registers for a tournament:
{
  "eventType": "player.registered",
  "eventId": "evt_1642087800_jkl012",
  "timestamp": "2024-01-15T19:30:00Z",
  "tournamentId": "12345",
  "playerId": "tp_player_456",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "playerInfo": {
      "displayName": "PlayerName",
      "vipLevel": "gold",
      "preferredLanguage": "en"
    },
    "registrationDetails": {
      "registeredAt": "2024-01-15T19:30:00Z",
      "entryFee": 10.00,
      "entryCount": 1,
      "totalPaid": 10.00,
      "currency": "USD",
      "paymentMethod": "balance"
    },
    "tournamentInfo": {
      "name": "Daily Championship",
      "gameSlug": "crash-classic",
      "scheduledStart": "2024-01-15T20:00:00Z",
      "currentPlayers": 45,
      "maxPlayers": 100
    }
  }
}

Player Eliminated

Sent when a player is eliminated from the tournament:
{
  "eventType": "player.eliminated",
  "eventId": "evt_1642091400_mno345",
  "timestamp": "2024-01-15T20:45:00Z",
  "tournamentId": "12345",
  "playerId": "tp_player_789",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "playerInfo": {
      "displayName": "EliminatedPlayer",
      "vipLevel": "silver"
    },
    "eliminationDetails": {
      "eliminatedAt": "2024-01-15T20:45:00Z",
      "finalPosition": 23,
      "finalScore": 850,
      "eliminationRound": 3,
      "eliminationReason": "insufficient_score",
      "survivalTime": 2700
    },
    "participation": {
      "entryCount": 1,
      "totalPaid": 10.00,
      "rebuyCount": 0
    }
  }
}

Player Rebought

Sent when a player purchases additional tournament entries:
{
  "eventType": "player.rebought",
  "eventId": "evt_1642090200_pqr678",
  "timestamp": "2024-01-15T20:10:00Z",
  "tournamentId": "12345",
  "playerId": "tp_player_456",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "playerInfo": {
      "displayName": "PlayerName",
      "currentPosition": 25,
      "currentScore": 680
    },
    "rebuyDetails": {
      "rebuyAmount": 10.00,
      "newEntryCount": 2,
      "totalPaid": 20.00,
      "remainingRebuys": 1,
      "maxEntries": 3,
      "rebuyProcessedAt": "2024-01-15T20:10:00Z"
    }
  }
}

Game Events

Round Started

Sent when a new tournament round begins:
{
  "eventType": "round.started",
  "eventId": "evt_1642090800_stu901",
  "timestamp": "2024-01-15T20:20:00Z",
  "tournamentId": "12345",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "roundNumber": 3,
    "roundStartTime": "2024-01-15T20:20:00Z",
    "activePlayers": 45,
    "eliminatedPlayers": 42,
    "roundDuration": 300,
    "eliminationTarget": 22,
    "gameConfig": {
      "eliminationRate": 0.5,
      "minCrashMultiplier": 1.2,
      "maxCrashMultiplier": 50.0
    }
  }
}

Round Completed

Sent when a tournament round finishes:
{
  "eventType": "round.completed",
  "eventId": "evt_1642091100_vwx234",
  "timestamp": "2024-01-15T20:25:00Z",
  "tournamentId": "12345",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "roundNumber": 3,
    "roundCompletedAt": "2024-01-15T20:25:00Z",
    "actualDuration": 295,
    "playersAtStart": 45,
    "playersRemaining": 23,
    "playersEliminated": 22,
    "roundWinner": {
      "playerId": "tp_player_123",
      "displayName": "RoundWinner",
      "roundScore": 450,
      "cumulativeScore": 1680
    },
    "topPerformers": [
      {
        "playerId": "tp_player_123",
        "score": 450,
        "position": 1
      },
      {
        "playerId": "tp_player_456",
        "score": 420,
        "position": 2
      }
    ]
  }
}

SQS Event Processing

SQS Consumer Implementation

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

// Webhook secret key (keep secure)
const WEBHOOK_SECRET = 'your-webhook-secret-key';

// Verify webhook signature
const verifyWebhookSignature = (req, res, next) => {
  const signature = req.headers['x-tournament-signature'];
  const body = JSON.stringify(req.body);
  
  if (!signature) {
    return res.status(401).json({ error: 'Missing signature' });
  }
  
  const expectedSignature = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(body)
    .digest('hex');
  
  const receivedSignature = signature.replace('sha256=', '');
  
  if (!crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  )) {
    return res.status(401).json({ error: 'Invalid signature' });
  }
  
  next();
};

// Webhook handler
app.post('/webhooks/tournaments', verifyWebhookSignature, (req, res) => {
  const event = req.body;
  
  console.log('Received webhook:', event.eventType, event.eventId);
  
  try {
    // Handle the event based on type
    switch (event.eventType) {
      case 'tournament.started':
        handleTournamentStarted(event);
        break;
        
      case 'tournament.completed':
        handleTournamentCompleted(event);
        break;
        
      case 'player.eliminated':
        handlePlayerEliminated(event);
        break;
        
      case 'player.registered':
        handlePlayerRegistered(event);
        break;
        
      case 'player.rebought':
        handlePlayerRebought(event);
        break;
        
      case 'tournament.cancelled':
        handleTournamentCancelled(event);
        break;
        
      default:
        console.log('Unknown event type:', event.eventType);
    }
    
    // Return success response
    res.status(200).json({ 
      received: true, 
      eventId: event.eventId,
      processedAt: new Date().toISOString()
    });
    
  } catch (error) {
    console.error('Webhook processing failed:', error);
    
    // Return error response to trigger retry
    res.status(500).json({ 
      error: 'Processing failed',
      eventId: event.eventId 
    });
  }
});

// Event handlers
const handleTournamentStarted = (event) => {
  const { tournamentId, data } = event;
  
  console.log(`Tournament ${tournamentId} started with ${data.initialPlayerCount} players`);
  
  // Update tournament status in your database
  updateTournamentStatus(tournamentId, 'in_progress', {
    actualStartTime: data.actualStartTime,
    playerCount: data.initialPlayerCount,
    prizePool: data.prizePool
  });
  
  // Send notifications to players
  notifyTournamentStart(tournamentId, data);
  
  // Update analytics
  trackEvent('tournament_started', {
    tournamentId: tournamentId,
    playerCount: data.initialPlayerCount,
    prizePool: data.prizePool.totalAmount
  });
};

const handleTournamentCompleted = (event) => {
  const { tournamentId, data } = event;
  
  console.log(`Tournament ${tournamentId} completed with ${data.winners.length} winners`);
  
  // Update tournament status
  updateTournamentStatus(tournamentId, 'completed', {
    completedAt: data.completedAt,
    duration: data.duration,
    totalPrizeDistributed: data.prizePool.distributed
  });
  
  // Process prize distributions
  processPrizeDistributions(tournamentId, data.winners);
  
  // Update player statistics
  updatePlayerStats(data.winners);
  
  // Send result notifications
  notifyTournamentResults(tournamentId, data.winners);
  
  // Track completion metrics
  trackEvent('tournament_completed', {
    tournamentId: tournamentId,
    duration: data.duration,
    playerCount: data.totalPlayers,
    totalPrizes: data.prizePool.distributed
  });
};

const handlePlayerEliminated = (event) => {
  const { tournamentId, playerId, data } = event;
  
  console.log(`Player ${playerId} eliminated from tournament ${tournamentId} at position ${data.eliminationDetails.finalPosition}`);
  
  // Update player status
  updatePlayerTournamentStatus(tournamentId, playerId, 'eliminated', {
    finalPosition: data.eliminationDetails.finalPosition,
    finalScore: data.eliminationDetails.finalScore,
    eliminatedAt: data.eliminationDetails.eliminatedAt
  });
  
  // Send elimination notification
  notifyPlayerElimination(playerId, tournamentId, data.eliminationDetails);
  
  // Update player statistics
  updatePlayerStats(playerId, {
    tournamentPlayed: 1,
    finalPosition: data.eliminationDetails.finalPosition,
    amountSpent: data.participation.totalPaid
  });
};

const handlePlayerRebought = (event) => {
  const { tournamentId, playerId, data } = event;
  
  console.log(`Player ${playerId} rebought in tournament ${tournamentId} for $${data.rebuyDetails.rebuyAmount}`);
  
  // Process rebuy payment
  processRebuyPayment(playerId, data.rebuyDetails.rebuyAmount);
  
  // Update player entry count
  updatePlayerEntries(tournamentId, playerId, data.rebuyDetails.newEntryCount);
  
  // Track rebuy analytics
  trackEvent('player_rebuy', {
    tournamentId: tournamentId,
    playerId: playerId,
    rebuyAmount: data.rebuyDetails.rebuyAmount,
    totalEntries: data.rebuyDetails.newEntryCount
  });
};

const handleTournamentCancelled = (event) => {
  const { tournamentId, data } = event;
  
  console.log(`Tournament ${tournamentId} cancelled: ${data.reason}`);
  
  // Update tournament status
  updateTournamentStatus(tournamentId, 'cancelled', {
    cancelledAt: data.cancelledAt,
    reason: data.reason
  });
  
  // Process refunds
  processRefunds(tournamentId, data.refunds);
  
  // Notify affected players
  notifyTournamentCancellation(tournamentId, data);
};

// Helper functions
const updateTournamentStatus = async (tournamentId, status, data) => {
  // Update tournament in your database
  console.log(`Updating tournament ${tournamentId} status to ${status}`);
};

const processPrizeDistributions = async (tournamentId, winners) => {
  for (const winner of winners) {
    // Credit prize amount to player account
    console.log(`Crediting $${winner.prizeAmount} to player ${winner.playerId}`);
  }
};

const notifyTournamentStart = async (tournamentId, data) => {
  // Send push notifications, emails, etc.
  console.log(`Sending tournament start notifications for ${tournamentId}`);
};

const trackEvent = (eventType, data) => {
  // Send to analytics service
  console.log(`Tracking event: ${eventType}`, data);
};

app.listen(3000, () => {
  console.log('Webhook server listening on port 3000');
});

Webhook Testing

Test Webhook Endpoint

POST /api/webhooks/{webhookId}/test
Authorization: Bearer {your-api-key}
Content-Type: application/json

{
  "eventType": "tournament.started",
  "data": {
    "tournamentName": "Test Tournament",
    "initialPlayerCount": 1
  }
}
Response:
{
  "testId": "test_1642089000_abc123",
  "webhookId": "wh_1234567890abcdef",
  "deliveryStatus": "success",
  "httpStatus": 200,
  "responseTime": 156,
  "deliveredAt": "2024-01-15T20:30:00Z"
}

Webhook Delivery Logs

GET /api/webhooks/{webhookId}/deliveries?limit=50
Authorization: Bearer {your-api-key}
Response:
{
  "deliveries": [
    {
      "deliveryId": "del_1642089000_xyz789",
      "eventType": "tournament.completed",
      "eventId": "evt_1642088000_abc123",
      "attempt": 1,
      "httpStatus": 200,
      "responseTime": 145,
      "deliveredAt": "2024-01-15T20:15:00Z",
      "success": true
    },
    {
      "deliveryId": "del_1642088500_def456",
      "eventType": "player.eliminated",
      "eventId": "evt_1642087500_ghi789",
      "attempt": 3,
      "httpStatus": 500,
      "responseTime": 5000,
      "deliveredAt": "2024-01-15T20:10:00Z",
      "success": false,
      "error": "Internal Server Error",
      "nextRetryAt": "2024-01-15T20:25:00Z"
    }
  ],
  "pagination": {
    "total": 247,
    "limit": 50,
    "offset": 0,
    "hasMore": true
  }
}

Error Handling & Reliability

Retry Strategy

The Tournament Platform implements automatic retry with exponential backoff:
AttemptDelayTotal Wait
1stImmediate0s
2nd5 seconds5s
3rd25 seconds30s
4th125 seconds2m 35s
5th625 seconds12m 20s

Error Response Handling

// Webhook endpoint should handle errors appropriately
app.post('/webhooks/tournaments', (req, res) => {
  try {
    const event = req.body;
    
    // Process event
    processWebhookEvent(event);
    
    // Return success
    res.status(200).json({ received: true });
    
  } catch (error) {
    console.error('Processing error:', error);
    
    if (error.code === 'TEMPORARY_FAILURE') {
      // Return 5xx to trigger retry
      res.status(503).json({ 
        error: 'Temporary failure, please retry',
        retryAfter: 30 
      });
    } else {
      // Return 4xx to prevent retry
      res.status(400).json({ 
        error: 'Permanent failure, do not retry',
        details: error.message 
      });
    }
  }
});

Monitoring Webhook Health

class WebhookHealthMonitor {
  constructor() {
    this.metrics = {
      totalDeliveries: 0,
      successfulDeliveries: 0,
      failedDeliveries: 0,
      averageResponseTime: 0
    };
    
    setInterval(() => {
      this.reportHealth();
    }, 60000); // Report every minute
  }
  
  recordDelivery(success, responseTime, error = null) {
    this.metrics.totalDeliveries++;
    
    if (success) {
      this.metrics.successfulDeliveries++;
    } else {
      this.metrics.failedDeliveries++;
      console.error('Webhook delivery failed:', error);
    }
    
    // Update average response time
    this.updateAverageResponseTime(responseTime);
  }
  
  updateAverageResponseTime(responseTime) {
    const count = this.metrics.totalDeliveries;
    this.metrics.averageResponseTime = 
      (this.metrics.averageResponseTime * (count - 1) + responseTime) / count;
  }
  
  reportHealth() {
    const successRate = this.metrics.successfulDeliveries / this.metrics.totalDeliveries;
    
    console.log('Webhook Health Report:', {
      totalDeliveries: this.metrics.totalDeliveries,
      successRate: (successRate * 100).toFixed(2) + '%',
      averageResponseTime: this.metrics.averageResponseTime.toFixed(0) + 'ms',
      failedDeliveries: this.metrics.failedDeliveries
    });
    
    // Alert if success rate drops below threshold
    if (successRate < 0.95) {
      console.warn('Webhook success rate below 95%!');
      // Send alert to monitoring system
    }
  }
}

Security Best Practices

Webhook Security

Signature Verification

Always verify HMAC-SHA256 signatures to ensure webhooks are from Tournament Platform

HTTPS Only

Only accept webhooks over encrypted HTTPS connections

IP Allowlisting

Restrict webhook endpoints to Tournament Platform IP addresses

Secret Rotation

Regularly rotate webhook secret keys and update configurations

Idempotency Handling

const processedEvents = new Set();

const handleWebhook = (req, res) => {
  const event = req.body;
  
  // Check if event already processed
  if (processedEvents.has(event.eventId)) {
    console.log('Event already processed:', event.eventId);
    return res.status(200).json({ 
      received: true, 
      status: 'already_processed' 
    });
  }
  
  try {
    // Process event
    processEvent(event);
    
    // Mark as processed
    processedEvents.add(event.eventId);
    
    res.status(200).json({ 
      received: true, 
      status: 'processed' 
    });
    
  } catch (error) {
    // Don't mark as processed if failed
    console.error('Processing failed:', error);
    res.status(500).json({ error: 'Processing failed' });
  }
};

Next Steps