> ## Documentation Index
> Fetch the complete documentation index at: https://docs-ts.playservices.tech/llms.txt
> Use this file to discover all available pages before exploring further.

# Event Reporting

> SQS event reporting patterns, event types, and reliable delivery mechanisms for external system integration

# Event Reporting

Event Reporting enables the Game Platform to send real-time tournament events to external systems via AWS SQS queues. This provides reliable, asynchronous communication for webhook delivery, analytics, and business logic integration.

## SQS Event Architecture

```mermaid theme={null}
graph TD
    A[Game Platform] -->|Send Events| B[AWS SQS Queue]
    B -->|Process Events| C[Tournament System Event Processor]
    C -->|HTTP Webhooks| D[Casino System]
    C -->|Analytics Events| E[Data Warehouse]
    C -->|Business Logic| F[Notification Service]
    
    style A fill:#e8f5e9
    style B fill:#fff8e1
    style C fill:#f3e5f5
    style D fill:#e1f5fe
    style E fill:#fce4ec
    style F fill:#e8eaf6
```

### Event Flow

1. **Game Platform** generates tournament events during gameplay
2. **SQS Queue** receives events for reliable, ordered processing

## Tournament Platform Event Notifications

<Info>
  **New Feature**: Tournament Platform now sends automatic event notifications to Game Platform for tournament lifecycle events.
</Info>

The Tournament Platform proactively notifies the Game Platform about important tournament events through HTTP webhooks. This enables real-time synchronization and automatic game room management.

### Configuration

Add the Game Platform URL to your Tournament Platform environment:

```bash theme={null}
# .env
GAME_PLATFORM_URL=https://gameplatform.example.com
```

### Supported Events

#### Tournament Start Event

Sent when a tournament begins (manual start or scheduled start):

```http theme={null}
POST {GAME_PLATFORM_URL}/internal/tournament/events
Content-Type: application/json

{
  "event": "tournament.started",
  "tournament_id": "12345",
  "tournament_name": "Daily Championship",
  "game_slug": "crash-classic",
  "started_at": "2024-01-15T20:00:00Z",
  "player_count": 87,
  "tournament_config": {
    "tournament_type": "points_based",
    "max_players": 100,
    "entry_settings": {
      "rebuy_enabled": true,
      "max_entries_per_player": 3
    }
  }
}
```

#### Tournament Cancellation Event

Sent when a tournament is cancelled (scheduled or in-progress):

```http theme={null}
POST {GAME_PLATFORM_URL}/internal/tournament/events
Content-Type: application/json

{
  "event": "tournament.cancelled",
  "tournament_id": "12345",
  "tournament_name": "Daily Championship", 
  "game_slug": "crash-classic",
  "cancelled_at": "2024-01-15T19:45:00Z",
  "reason": "Insufficient player registration",
  "status_before_cancellation": "scheduled"
}
```

### Event Reliability

* **Non-blocking**: Event notifications don't block tournament operations
* **Error Handling**: Failed notifications are logged but don't affect tournaments
* **Retry Logic**: Automatic retries with exponential backoff
* **Comprehensive Logging**: All event notifications are logged for debugging

3. **Event Processor** consumes events and triggers appropriate actions
4. **External Systems** receive notifications via webhooks or API calls

## SQS Event Types

### Tournament Lifecycle Events

#### Tournament Started

```json theme={null}
{
  "eventType": "room.tournament.started",
  "timestamp": "2024-01-15T20:02:15Z",
  "tournamentId": "12345",
  "roomId": "room_abc123",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "actualStartTime": "2024-01-15T20:02:15Z",
    "playerCount": 87,
    "gameSlug": "crash-classic",
    "estimatedDuration": 4500
  },
  "metadata": {
    "gameVersion": "1.2.3",
    "serverRegion": "us-east-1"
  }
}
```

#### Tournament Completed

```json theme={null}
{
  "eventType": "room.tournament.completed",
  "timestamp": "2024-01-15T21:15:30Z",
  "tournamentId": "12345",
  "roomId": "room_abc123",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "completedAt": "2024-01-15T21:15:30Z",
    "duration": 4395,
    "finalPlayerCount": 87,
    "totalRounds": 5,
    "prizePoolTotal": 870.00,
    "topPlayers": [
      {
        "playerId": "tp_player_456",
        "position": 1,
        "points": 2150,
        "prizeAmount": 435.00
      },
      {
        "playerId": "tp_player_789",
        "position": 2,
        "points": 1980,
        "prizeAmount": 261.00
      }
    ]
  }
}
```

### Player Events

#### Player Connected

```json theme={null}
{
  "eventType": "room.player.connected",
  "timestamp": "2024-01-15T19:45:20Z",
  "tournamentId": "12345",
  "playerId": "tp_player_456",
  "roomId": "room_abc123",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "displayName": "PlayerName",
    "connectionTime": "2024-01-15T19:45:20Z",
    "ipAddress": "192.168.1.100",
    "userAgent": "Mozilla/5.0...",
    "entryCount": 2,
    "totalPaid": 20.00
  }
}
```

#### Player Score Updated

```json theme={null}
{
  "eventType": "player.score.updated",
  "timestamp": "2024-01-15T20:15:45Z",
  "tournamentId": "12345",
  "playerId": "tp_player_456",
  "roomId": "room_abc123",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "previousScore": 1100,
    "newScore": 1250,
    "scoreDelta": 150,
    "currentPosition": 15,
    "previousPosition": 18,
    "round": 3,
    "gameAction": "successful_cashout"
  }
}
```

#### Player Eliminated

```json theme={null}
{
  "eventType": "player.eliminated",
  "timestamp": "2024-01-15T20:45:30Z",
  "tournamentId": "12345",
  "playerId": "tp_player_789",
  "roomId": "room_abc123",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "eliminatedAt": "2024-01-15T20:45:30Z",
    "finalPosition": 23,
    "finalScore": 850,
    "eliminationRound": 3,
    "eliminationReason": "insufficient_score",
    "survivalTime": 2610
  }
}
```

### Game Events

#### Round Started

```json theme={null}
{
  "eventType": "game.round.started",
  "timestamp": "2024-01-15T20:20:00Z",
  "tournamentId": "12345",
  "roomId": "room_abc123",
  "operatorId": "op_1234567890abcdef",
  "data": {
    "roundNumber": 3,
    "roundStartTime": "2024-01-15T20:20:00Z",
    "activePlayers": 67,
    "eliminatedPlayers": 20,
    "roundDuration": 300,
    "gameConfig": {
      "eliminationRate": 0.3,
      "minCrashMultiplier": 1.2
    }
  }
}
```

## Event Publishing Implementation

### SQS Event Publisher

```javascript theme={null}
const AWS = require('aws-sdk');

class SQSEventPublisher {
  constructor(queueUrl, region = 'us-east-1') {
    this.sqs = new AWS.SQS({ region });
    this.queueUrl = queueUrl;
    this.batchSize = 10; // SQS batch limit
    this.messageQueue = [];
  }
  
  async publishEvent(eventType, tournamentId, data, metadata = {}) {
    const event = {
      eventType: eventType,
      timestamp: new Date().toISOString(),
      tournamentId: tournamentId,
      eventId: this.generateEventId(),
      data: data,
      metadata: {
        source: 'game-platform',
        version: '1.0',
        ...metadata
      }
    };
    
    try {
      await this.sendToSQS(event);
      console.log(`Published event: ${eventType} for tournament ${tournamentId}`);
    } catch (error) {
      console.error('Failed to publish event:', error);
      throw error;
    }
  }
  
  async sendToSQS(event) {
    const params = {
      QueueUrl: this.queueUrl,
      MessageBody: JSON.stringify(event),
      MessageAttributes: {
        'EventType': {
          DataType: 'String',
          StringValue: event.eventType
        },
        'TournamentId': {
          DataType: 'String', 
          StringValue: event.tournamentId
        },
        'OperatorId': {
          DataType: 'String',
          StringValue: event.operatorId || 'unknown'
        }
      },
      MessageGroupId: event.tournamentId, // For FIFO queues
      MessageDeduplicationId: event.eventId
    };
    
    return await this.sqs.sendMessage(params).promise();
  }
  
  generateEventId() {
    return `evt_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
  }
  
  // Batch publishing for high-volume events
  queueEvent(event) {
    this.messageQueue.push(event);
    
    if (this.messageQueue.length >= this.batchSize) {
      this.flushBatch();
    }
  }
  
  async flushBatch() {
    if (this.messageQueue.length === 0) return;
    
    const batch = this.messageQueue.splice(0, this.batchSize);
    
    const params = {
      QueueUrl: this.queueUrl,
      Entries: batch.map((event, index) => ({
        Id: index.toString(),
        MessageBody: JSON.stringify(event),
        MessageGroupId: event.tournamentId,
        MessageDeduplicationId: event.eventId,
        MessageAttributes: {
          'EventType': {
            DataType: 'String',
            StringValue: event.eventType
          }
        }
      }))
    };
    
    try {
      await this.sqs.sendMessageBatch(params).promise();
      console.log(`Batch published ${batch.length} events`);
    } catch (error) {
      console.error('Batch publish failed:', error);
      // Re-queue failed events
      this.messageQueue.unshift(...batch);
    }
  }
}
```

### Tournament Event Manager

```javascript theme={null}
class TournamentEventManager {
  constructor(publisher) {
    this.publisher = publisher;
    this.eventBuffer = new Map();
    this.flushInterval = setInterval(() => {
      this.flushBufferedEvents();
    }, 1000); // Flush every second
  }
  
  // Tournament lifecycle events
  async tournamentStarted(tournamentId, roomId, startData) {
    await this.publisher.publishEvent(
      'room.tournament.started',
      tournamentId,
      {
        roomId: roomId,
        actualStartTime: new Date().toISOString(),
        playerCount: startData.playerCount,
        gameSlug: startData.gameSlug,
        estimatedDuration: startData.estimatedDuration
      }
    );
  }
  
  async tournamentCompleted(tournamentId, roomId, results) {
    await this.publisher.publishEvent(
      'room.tournament.completed',
      tournamentId,
      {
        roomId: roomId,
        completedAt: new Date().toISOString(),
        duration: results.duration,
        finalPlayerCount: results.playerCount,
        totalRounds: results.rounds,
        prizePoolTotal: results.prizePool,
        topPlayers: results.winners.slice(0, 3) // Top 3 players
      }
    );
  }
  
  // Player events
  async playerConnected(tournamentId, playerId, connectionData) {
    await this.publisher.publishEvent(
      'room.player.connected',
      tournamentId,
      {
        playerId: playerId,
        displayName: connectionData.displayName,
        connectionTime: new Date().toISOString(),
        ipAddress: connectionData.ipAddress,
        entryCount: connectionData.entryCount,
        totalPaid: connectionData.totalPaid
      }
    );
  }
  
  // Buffered score updates to reduce event volume
  bufferScoreUpdate(tournamentId, playerId, scoreData) {
    const key = `${tournamentId}_${playerId}`;
    
    this.eventBuffer.set(key, {
      eventType: 'player.score.updated',
      tournamentId: tournamentId,
      playerId: playerId,
      data: scoreData,
      lastUpdate: new Date()
    });
  }
  
  async flushBufferedEvents() {
    const events = Array.from(this.eventBuffer.values());
    this.eventBuffer.clear();
    
    for (const event of events) {
      try {
        await this.publisher.publishEvent(
          event.eventType,
          event.tournamentId,
          {
            playerId: event.playerId,
            timestamp: event.lastUpdate.toISOString(),
            ...event.data
          }
        );
      } catch (error) {
        console.error('Failed to flush buffered event:', error);
      }
    }
  }
  
  async playerEliminated(tournamentId, playerId, eliminationData) {
    await this.publisher.publishEvent(
      'player.eliminated',
      tournamentId,
      {
        playerId: playerId,
        eliminatedAt: new Date().toISOString(),
        finalPosition: eliminationData.position,
        finalScore: eliminationData.score,
        eliminationRound: eliminationData.round,
        eliminationReason: eliminationData.reason,
        survivalTime: eliminationData.survivalTime
      }
    );
  }
}
```

### Game Integration Example

```javascript theme={null}
class CrashGameEventIntegration {
  constructor(eventManager) {
    this.eventManager = eventManager;
  }
  
  // Game-specific event handlers
  async onGameRoundStart(tournamentId, roundData) {
    await this.eventManager.publisher.publishEvent(
      'game.round.started',
      tournamentId,
      {
        roundNumber: roundData.roundNumber,
        roundStartTime: new Date().toISOString(),
        activePlayers: roundData.activePlayers,
        eliminatedPlayers: roundData.eliminatedPlayers,
        roundDuration: roundData.duration,
        gameConfig: roundData.config
      }
    );
  }
  
  async onCrashEvent(tournamentId, crashData) {
    await this.eventManager.publisher.publishEvent(
      'game.crash.occurred',
      tournamentId,
      {
        crashMultiplier: crashData.multiplier,
        crashTime: crashData.crashTime,
        playersActive: crashData.playersActive,
        playersCashedOut: crashData.playersCashedOut,
        totalWinnings: crashData.totalWinnings
      }
    );
  }
  
  onPlayerAction(tournamentId, playerId, action, actionData) {
    // Buffer high-frequency events
    this.eventManager.bufferScoreUpdate(tournamentId, playerId, {
      action: action,
      actionData: actionData,
      timestamp: new Date().toISOString()
    });
  }
}
```

## Error Handling & Reliability

### Dead Letter Queue Handling

```javascript theme={null}
class ReliableEventPublisher extends SQSEventPublisher {
  constructor(queueUrl, dlqUrl, region = 'us-east-1') {
    super(queueUrl, region);
    this.dlqUrl = dlqUrl;
    this.retryAttempts = 3;
    this.retryDelay = 1000; // 1 second
  }
  
  async publishEventWithRetry(eventType, tournamentId, data, metadata = {}) {
    let lastError;
    
    for (let attempt = 1; attempt <= this.retryAttempts; attempt++) {
      try {
        await this.publishEvent(eventType, tournamentId, data, metadata);
        return; // Success
      } catch (error) {
        lastError = error;
        console.warn(`Event publish attempt ${attempt} failed:`, error.message);
        
        if (attempt < this.retryAttempts) {
          await this.delay(this.retryDelay * Math.pow(2, attempt - 1)); // Exponential backoff
        }
      }
    }
    
    // All retries failed - send to DLQ
    console.error(`Event publish failed after ${this.retryAttempts} attempts:`, lastError);
    await this.sendToDeadLetterQueue({
      originalEvent: { eventType, tournamentId, data, metadata },
      error: lastError.message,
      attempts: this.retryAttempts,
      timestamp: new Date().toISOString()
    });
  }
  
  async sendToDeadLetterQueue(failedEvent) {
    try {
      await this.sqs.sendMessage({
        QueueUrl: this.dlqUrl,
        MessageBody: JSON.stringify(failedEvent)
      }).promise();
      
      console.log('Event sent to dead letter queue');
    } catch (error) {
      console.error('Failed to send to dead letter queue:', error);
    }
  }
  
  delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}
```

### Event Monitoring

```javascript theme={null}
class EventMetrics {
  constructor() {
    this.metrics = {
      published: 0,
      failed: 0,
      retries: 0,
      averageLatency: 0,
      eventTypes: new Map()
    };
    
    this.startTime = Date.now();
    
    // Report metrics every 60 seconds
    setInterval(() => {
      this.reportMetrics();
    }, 60000);
  }
  
  recordPublished(eventType, latencyMs) {
    this.metrics.published++;
    
    // Track by event type
    const typeCount = this.metrics.eventTypes.get(eventType) || 0;
    this.metrics.eventTypes.set(eventType, typeCount + 1);
    
    // Update average latency
    this.updateAverageLatency(latencyMs);
  }
  
  recordFailed(eventType, error) {
    this.metrics.failed++;
    console.error(`Event publication failed - ${eventType}:`, error);
  }
  
  recordRetry(eventType) {
    this.metrics.retries++;
  }
  
  updateAverageLatency(latencyMs) {
    const count = this.metrics.published;
    this.metrics.averageLatency = 
      (this.metrics.averageLatency * (count - 1) + latencyMs) / count;
  }
  
  reportMetrics() {
    const uptime = Date.now() - this.startTime;
    const publishRate = (this.metrics.published / (uptime / 1000)).toFixed(2);
    
    console.log('Event Metrics Report:');
    console.log(`  Published: ${this.metrics.published}`);
    console.log(`  Failed: ${this.metrics.failed}`);
    console.log(`  Retries: ${this.metrics.retries}`);
    console.log(`  Publish Rate: ${publishRate} events/second`);
    console.log(`  Average Latency: ${this.metrics.averageLatency.toFixed(2)}ms`);
    
    // Reset counters
    Object.assign(this.metrics, {
      published: 0,
      failed: 0,
      retries: 0,
      eventTypes: new Map()
    });
  }
}
```

## Testing & Debugging

### Event Testing Framework

```javascript theme={null}
class EventTestFramework {
  constructor() {
    this.publishedEvents = [];
    this.mockPublisher = {
      publishEvent: this.mockPublishEvent.bind(this)
    };
  }
  
  async mockPublishEvent(eventType, tournamentId, data, metadata) {
    const event = {
      eventType,
      tournamentId,
      data,
      metadata,
      timestamp: new Date().toISOString()
    };
    
    this.publishedEvents.push(event);
    console.log('Mock published event:', event);
    return event;
  }
  
  getPublishedEvents(eventType = null) {
    if (eventType) {
      return this.publishedEvents.filter(e => e.eventType === eventType);
    }
    return [...this.publishedEvents];
  }
  
  clearEvents() {
    this.publishedEvents = [];
  }
  
  assertEventPublished(eventType, tournamentId) {
    const found = this.publishedEvents.find(e => 
      e.eventType === eventType && e.tournamentId === tournamentId
    );
    
    if (!found) {
      throw new Error(`Expected event ${eventType} for tournament ${tournamentId} was not published`);
    }
    
    return found;
  }
}

// Usage in tests
const testFramework = new EventTestFramework();
const eventManager = new TournamentEventManager(testFramework.mockPublisher);

// Test tournament started event
await eventManager.tournamentStarted('12345', 'room_abc123', {
  playerCount: 50,
  gameSlug: 'crash-classic',
  estimatedDuration: 3600
});

// Assert event was published
const event = testFramework.assertEventPublished('room.tournament.started', '12345');
console.log('Event published successfully:', event);
```

## Next Steps

<CardGroup cols={2}>
  <Card title="Casino Integration" icon="building" href="/casino-integration/webhooks-events">
    Learn how Casino Systems receive and process tournament events via webhooks.
  </Card>

  <Card title="Database Schema" icon="database" href="/database/schema-overview">
    Explore the database schema supporting the tournament platform architecture.
  </Card>
</CardGroup>
