> ## 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.

# Webhooks & Events

> Real-time tournament event notifications, webhook configuration, and event handling patterns for external systems

# 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

```mermaid theme={null}
graph TD
    A[Tournament System] -->|SQS Events| B[Event Processor]
    B -->|HTTP Webhooks| C[Casino System]
    B -->|Retry Logic| D[Failed Event Queue]
    D -->|Exponential Backoff| C
    C -->|200 OK| B
    C -->|4xx/5xx Error| D
    
    style A fill:#e8f5e9
    style B fill:#fff8e1
    style C fill:#e1f5fe
    style D fill:#fce4ec
```

### 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 [support@playservices.tech](mailto:support@playservices.tech) 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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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:

```json theme={null}
{
  "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

```javascript theme={null}
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

```http theme={null}
POST /api/webhooks/{webhookId}/test
Authorization: Bearer {your-api-key}
Content-Type: application/json

{
  "eventType": "tournament.started",
  "data": {
    "tournamentName": "Test Tournament",
    "initialPlayerCount": 1
  }
}
```

**Response:**

```json theme={null}
{
  "testId": "test_1642089000_abc123",
  "webhookId": "wh_1234567890abcdef",
  "deliveryStatus": "success",
  "httpStatus": 200,
  "responseTime": 156,
  "deliveredAt": "2024-01-15T20:30:00Z"
}
```

#### Webhook Delivery Logs

```http theme={null}
GET /api/webhooks/{webhookId}/deliveries?limit=50
Authorization: Bearer {your-api-key}
```

**Response:**

```json theme={null}
{
  "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:

| Attempt | Delay       | Total Wait |
| ------- | ----------- | ---------- |
| 1st     | Immediate   | 0s         |
| 2nd     | 5 seconds   | 5s         |
| 3rd     | 25 seconds  | 30s        |
| 4th     | 125 seconds | 2m 35s     |
| 5th     | 625 seconds | 12m 20s    |

### Error Response Handling

```javascript theme={null}
// 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

```javascript theme={null}
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

<CardGroup cols={2}>
  <Card title="Signature Verification" icon="shield-check">
    Always verify HMAC-SHA256 signatures to ensure webhooks are from Tournament Platform
  </Card>

  <Card title="HTTPS Only" icon="lock">
    Only accept webhooks over encrypted HTTPS connections
  </Card>

  <Card title="IP Allowlisting" icon="globe">
    Restrict webhook endpoints to Tournament Platform IP addresses
  </Card>

  <Card title="Secret Rotation" icon="key">
    Regularly rotate webhook secret keys and update configurations
  </Card>
</CardGroup>

### Idempotency Handling

```javascript theme={null}
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

<CardGroup cols={2}>
  <Card title="Testing & Debugging" icon="bug" href="/casino-integration/testing-debugging">
    Tools and techniques for testing your tournament integration implementation.
  </Card>

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