Additional features

Webhook Integration Guide

Overview

This guide explains how to integrate with our webhook system to receive real-time notifications about prize payout events. Webhooks allow you to stay informed about prize payouts as they happen, enabling you to update your systems, send notifications, or trigger other actions.

What You'll Receive

When you configure a webhook endpoint, you'll receive HTTP POST requests containing JSON payloads with information about prize payout events. Each webhook can handle multiple types of events, making it efficient to process different stages of the prize payout lifecycle.

Supported Event Types

Event Type

Description

When You'll Receive It

PRIZE_PAYOUT_CREATED

Prize payout has been created

When a new prize payout is created

PRIZE_PAYOUT_SUCCEEDED

Prize payout has been successfully processed

When a prize payout completes successfully

PRIZE_PAYOUT_FAILED

Prize payout has failed

When a prize payout fails after retries

PLAYER_OPTED_IN

Player has opted into a campaign

When a player opts into a campaign

PLAYER_OPTED_OUT

Player has opted out of a campaign

When a player opts out of a campaign

Setting Up Your Webhook Endpoint

1. Create Your Endpoint

Create a public HTTP endpoint that can receive POST requests. Your endpoint should:

  • Accept POST requests

  • Return a 2xx status code (200, 201, 202) to acknowledge receipt

  • Handle JSON payloads

  • Be idempotent (safe to receive duplicate requests)

2. Example Endpoint (Node.js/Express)

const express = require("express");
const app = express();

app.use(express.json());

app.post("/webhooks/prizes", (req, res) => {
  const payload = req.body;

  // Process the webhook payload
  console.log("Received webhook:", payload);

  // Your business logic here
  processWebhookEvent(payload);

  // Always return a 2xx status code
  res.status(200).json({ received: true });
});

function processWebhookEvent(payload) {
  switch (payload.event_type) {
    case "PRIZE_PAYOUT_CREATED":
      handlePrizePayoutCreated(payload);
      break;
    case "PRIZE_PAYOUT_SUCCEEDED":
      handlePrizePayoutSucceeded(payload);
      break;
    case "PRIZE_PAYOUT_FAILED":
      handlePrizePayoutFailed(payload);
      break;
    case "PLAYER_OPTED_IN":
      handlePlayerOptedIn(payload);
      break;
    case "PLAYER_OPTED_OUT":
      handlePlayerOptedOut(payload);
      break;
  }
}

3. Example Endpoint (Python/Flask)

from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route('/webhooks/prizes', methods=['POST'])
def webhook_handler():
    payload = request.get_json()

    # Process the webhook payload
    print(f"Received webhook: {payload}")

    # Your business logic here
    process_webhook_event(payload)

    # Always return a 2xx status code
    return jsonify({"received": True}), 200

def process_webhook_event(payload):
    event_type = payload.get('event_type')

    if event_type == 'PRIZE_PAYOUT_CREATED':
        handle_prize_payout_created(payload)
    elif event_type == 'PRIZE_PAYOUT_SUCCEEDED':
        handle_prize_payout_succeeded(payload)
    elif event_type == 'PRIZE_PAYOUT_FAILED':
        handle_prize_payout_failed(payload)
    elif event_type == 'PLAYER_OPTED_IN':
        handle_player_opted_in(payload)
    elif event_type == 'PLAYER_OPTED_OUT':
        handle_player_opted_out(payload)

Webhook Payload Format

All webhooks send JSON payloads with this structure:

{
  "campaign_id": "campaign-123",
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "player_id": "player-hash-abc123",
  "player_currency": "USD",
  "event_type": "PRIZE_PAYOUT_CREATED",
  "status": "TODO",
  "prize_type": "cash",
  "amount": 100.5,
  "bonus_id": 456
}

Payload Fields

Field

Type

Description

campaign_id

string

Unique campaign identifier

request_id

string

Unique request identifier (UUID)

player_id

string

Player hash identifier

player_currency

string

Player's currency (e.g., "USD", "EUR")

event_type

string

Type of event that triggered the webhook

status

string

Current status of the prize payout

prize_type

string

Type of prize (cash, free_spins, bonus_amount, etc.)

amount

number

Prize amount (for applicable prize types)

bonus_id

number

Optional bonus identifier

game_id

number

Game ID (for free spins)

percentage_value

number

Percentage value (for deposit bonuses)

item

string

Item name (for catalog items)

value

number

Item value (for catalog items)

Prize Type Examples

Different prize types include different fields in the payload:

Cash Prize

{
  "campaign_id": "summer-campaign-2024",
  "request_id": "550e8400-e29b-41d4-a716-446655440000",
  "player_id": "player-abc123",
  "player_currency": "USD",
  "event_type": "PRIZE_PAYOUT_SUCCEEDED",
  "status": "SUCCESS",
  "prize_type": "cash",
  "amount": 100.5,
  "bonus_id": 456
}

Free Spins

{
  "campaign_id": "slot-tournament-2024",
  "request_id": "550e8400-e29b-41d4-a716-446655440001",
  "player_id": "player-def456",
  "player_currency": "EUR",
  "event_type": "PRIZE_PAYOUT_CREATED",
  "status": "TODO",
  "prize_type": "free_spins",
  "amount": 50,
  "game_id": 123,
  "bonus_id": 789
}

Bonus Amount

{
  "campaign_id": "welcome-bonus",
  "request_id": "550e8400-e29b-41d4-a716-446655440002",
  "player_id": "player-ghi789",
  "player_currency": "GBP",
  "event_type": "PRIZE_PAYOUT_SUCCEEDED",
  "status": "SUCCESS",
  "prize_type": "bonus_amount",
  "amount": 75.25,
  "bonus_id": 101
}

Deposit Bonus

{
  "campaign_id": "deposit-match",
  "request_id": "550e8400-e29b-41d4-a716-446655440003",
  "player_id": "player-jkl012",
  "player_currency": "USD",
  "event_type": "PRIZE_PAYOUT_CREATED",
  "status": "TODO",
  "prize_type": "deposit_bonus",
  "percentage_value": 100.0,
  "bonus_id": 202
}

Catalog Item

{
  "campaign_id": "loyalty-rewards",
  "request_id": "550e8400-e29b-41d4-a716-446655440004",
  "player_id": "player-mno345",
  "player_currency": "USD",
  "event_type": "PRIZE_PAYOUT_SUCCEEDED",
  "status": "SUCCESS",
  "prize_type": "catalog_item",
  "item": "Gaming Headset",
  "value": 25.0,
  "bonus_id": 303
}

Player Opt-in/Out Events

Player opt-in and opt-out events have a simpler payload structure compared to prize payout events. These events are triggered when players choose to participate in or withdraw from campaigns.

Player Opt-in Event

{
  "player_id": "player-abc123",
  "campaign_id": "summer-campaign-2024",
  "event_type": "PLAYER_OPTED_IN"
}

Player Opt-out Event

{
  "player_id": "player-def456",
  "campaign_id": "winter-tournament-2024",
  "event_type": "PLAYER_OPTED_OUT"
}

Player Opt-in/Out Payload Fields

Field

Type

Description

player_id

string

Player hash identifier

campaign_id

string

Unique campaign identifier

event_type

string

Type of event (PLAYER_OPTED_IN or PLAYER_OPTED_OUT)

Authentication

If you provide an API key when setting up your webhook, we'll include it in the Authorization header:

Authorization: Bearer your-secret-api-key

Verifying Webhook Authenticity

To verify that webhooks are coming from us, check the Authorization header:

// Node.js example
app.post("/webhooks/prizes", (req, res) => {
  const authHeader = req.headers.authorization;
  const expectedToken = "Bearer your-secret-api-key";

  if (authHeader !== expectedToken) {
    return res.status(401).json({ error: "Unauthorized" });
  }

  // Process webhook...
});
# Python example
@app.route('/webhooks/prizes', methods=['POST'])
def webhook_handler():
    auth_header = request.headers.get('Authorization')
    expected_token = 'Bearer your-secret-api-key'

    if auth_header != expected_token:
        return jsonify({"error": "Unauthorized"}), 401

    // Process webhook...

HTTP Headers

All webhook requests include these headers:

Content-Type: application/json
Authorization: Bearer {api_key}  # if api_key is provided
User-Agent: Unibo-Webhooks/1.0

Best Practices

1. Always Return 2xx Status Codes

Your endpoint should return a 2xx status code (200, 201, 202) to acknowledge receipt. If you return an error status code, we'll log the failure but won't retry.

// Good - returns 200
res.status(200).json({ received: true });

// Good - returns 202 (Accepted)
res.status(202).json({ processing: true });

// Bad - returns 500 (will be logged as failure)
res.status(500).json({ error: "Something went wrong" });

2. Handle Duplicate Requests

Webhooks might be sent multiple times. Design your endpoint to handle this gracefully:

// Use request_id to prevent duplicate processing
const requestId = payload.request_id;

// Check if you've already processed this request
if (alreadyProcessed(requestId)) {
  return res.status(200).json({ already_processed: true });
}

// Process the webhook and store the request_id
processWebhook(payload);
storeProcessedRequest(requestId);

3. Process Webhooks Asynchronously

For better performance, process webhooks asynchronously:

app.post("/webhooks/prizes", (req, res) => {
  // Immediately acknowledge receipt
  res.status(202).json({ accepted: true });

  // Process asynchronously
  setImmediate(() => {
    processWebhookAsync(req.body);
  });
});

4. Implement Proper Error Handling

app.post("/webhooks/prizes", (req, res) => {
  try {
    const payload = req.body;

    // Validate payload
    if (!payload.event_type) {
      console.error("Invalid payload received:", payload);
      return res.status(400).json({ error: "Invalid payload" });
    }

    // Process webhook
    processWebhook(payload);

    res.status(200).json({ received: true });
  } catch (error) {
    console.error("Error processing webhook:", error);
    res.status(500).json({ error: "Internal server error" });
  }
});

Common Use Cases

1. Update Player Balance

function handlePrizePayoutSucceeded(payload) {
  if (payload.prize_type === "cash") {
    // Update player's balance in your system
    updatePlayerBalance(payload.player_id, payload.amount);

    // Send notification to player
    sendNotification(payload.player_id, `You received $${payload.amount}!`);
  }
}

function handlePrizePayoutCreated(payload) {
  // Log the new prize payout for tracking
  console.log(`New prize payout created: ${payload.request_id}`);

  // Update internal tracking system
  trackingService.createPayoutRecord(payload);

  // Send confirmation to player
  sendNotification(payload.player_id, "Your prize is being processed!");
}

function handlePrizePayoutFailed(payload) {
  // Log the failed payout
  console.error(`Prize payout failed: ${payload.request_id}`);

  // Update internal tracking system
  trackingService.markPayoutFailed(payload);

  // Notify support team
  supportService.createTicket({
    type: "payout_failed",
    player_id: payload.player_id,
    request_id: payload.request_id,
    campaign_id: payload.campaign_id,
  });
}

2. Track Prize Analytics

function trackPrizePayoutAnalytics(payload) {
  const analytics = {
    campaign_id: payload.campaign_id,
    player_id: payload.player_id,
    event_type: payload.event_type,
    prize_type: payload.prize_type,
    amount: payload.amount,
    timestamp: new Date().toISOString(),
  };

  // Send to analytics service
  analyticsService.track("prize_payout", analytics);
}

3. Trigger External Actions

function handlePrizePayoutSucceeded(payload) {
  // Trigger CRM update
  crmService.updatePlayer(payload.player_id, { last_prize: payload.amount });

  // Send to marketing automation
  marketingService.trigger("prize_won", payload);

  // Update loyalty program
  loyaltyService.addPoints(payload.player_id, payload.amount);
}

4. Track Player Campaign Participation

function handlePlayerOptedIn(payload) {
  // Update player's campaign participation status
  playerService.updateCampaignStatus(
    payload.player_id,
    payload.campaign_id,
    "opted_in"
  );

  // Send welcome message
  notificationService.send(
    payload.player_id,
    `Welcome to ${payload.campaign_id}!`
  );

  // Update analytics
  analyticsService.track("player_opted_in", {
    player_id: payload.player_id,
    campaign_id: payload.campaign_id,
    timestamp: new Date().toISOString(),
  });
}

function handlePlayerOptedOut(payload) {
  // Update player's campaign participation status
  playerService.updateCampaignStatus(
    payload.player_id,
    payload.campaign_id,
    "opted_out"
  );

  // Send opt-out confirmation
  notificationService.send(
    payload.player_id,
    "You've been removed from the campaign."
  );

  // Update analytics
  analyticsService.track("player_opted_out", {
    player_id: payload.player_id,
    campaign_id: payload.campaign_id,
    timestamp: new Date().toISOString(),
  });
}

Testing Your Webhook

1. Use a Webhook Testing Service

Services like webhook.site or ngrok can help you test webhooks during development.

2. Test Different Event Types

Make sure your endpoint handles all event types you've configured:

// Test payloads for different events
const testPayloads = {
  created: {
    event_type: "PRIZE_PAYOUT_CREATED",
    status: "TODO",
    // ... other fields
  },
  succeeded: {
    event_type: "PRIZE_PAYOUT_SUCCEEDED",
    status: "SUCCESS",
    // ... other fields
  },
  failed: {
    event_type: "PRIZE_PAYOUT_FAILED",
    status: "FAILED",
    // ... other fields
  },
  player_opted_in: {
    event_type: "PLAYER_OPTED_IN",
    player_id: "player-test-123",
    campaign_id: "test-campaign-2024",
  },
  player_opted_out: {
    event_type: "PLAYER_OPTED_OUT",
    player_id: "player-test-456",
    campaign_id: "test-campaign-2024",
  },
};

3. Test Error Scenarios

Test how your endpoint handles:

  • Invalid JSON

  • Missing required fields

  • Authentication failures

  • Network timeouts

Troubleshooting

Common Issues

  1. Webhook not received: Check that your endpoint is publicly accessible and returns 2xx status codes

  2. Authentication errors: Verify your API key is correct and properly formatted

  3. Payload parsing errors: Ensure your endpoint can handle JSON payloads

  4. Duplicate processing: Implement idempotency using the request_id field

Debugging Tips

  1. Log all incoming requests to see what you're receiving

  2. Check your server logs for any errors

  3. Verify your endpoint URL is correct and accessible

  4. Test with a simple endpoint first to ensure basic connectivity

Getting Help

If you're having issues with webhook integration:

  1. Check your server logs for error messages

  2. Verify your endpoint is accessible from the internet

  3. Test with a simple webhook testing service

  4. Contact our support team with specific error details

Rate Limits

Currently, there are no rate limits on webhook delivery. However, we recommend:

  • Processing webhooks quickly (within 30 seconds)

  • Implementing proper error handling

  • Using asynchronous processing for heavy operations

Security Considerations

  1. Use HTTPS: Always use HTTPS for your webhook endpoint

  2. Validate API Keys: Check the Authorization header for valid API keys

  3. Validate Payloads: Verify that payloads contain expected fields

  4. Implement Rate Limiting: Consider rate limiting on your endpoint

  5. Monitor for Abuse: Watch for unusual webhook activity

Was this helpful?