TOURNAMENTSUITE
TOURNAMENTSUITE
Developer Documentation
Get StartedPaginationResponse CodesRate LimitsWebhooks
Overview

Webhooks

Register endpoints to receive real-time event notifications from Tournament Suite.

Webhooks let you receive notifications when events occur in your project — instead of polling the API repeatedly. When a subscribed event fires, Tournament Suite sends an HTTP request to your endpoint with the event payload.

Set up a webhook

1. Register your endpoint

In your project's Settings → Developer → Webhooks, create a subscription with your endpoint URL, the events you want to receive, and a delivery configuration (retry limits, timeout, and optional circuit breaker settings — see Delivery configuration below).

Your endpoint must:

  • Be publicly reachable over HTTP or HTTPS (HTTPS is required in production)
  • Not resolve to a localhost, loopback, private-network, or link-local address

When you create the subscription, Tournament Suite validates that the URL is reachable and not pointed at an internal or private address. If you enable health checks in the delivery configuration, Tournament Suite also periodically issues a plain GET request to your endpoint (or a separate health-check URL you specify) and expects one of a configurable set of status codes back — there is no ownership-verification handshake or challenge exchange.

2. Save the signing secret

If you configure HMAC signing (see below), Tournament Suite generates a signing secret when the subscription is created. Store this secret securely — you will use it to verify the authenticity of every incoming webhook request.

3. Subscribe to events

Choose the event types your subscription should receive from the available events catalog. You can also scope a subscription with an aggregate-type filter, specific tournament IDs, specific user IDs, or custom field-matching conditions.

Authenticate outbound requests

Each subscription chooses one authentication mode for the requests Tournament Suite makes to your endpoint:

ModeBehavior
noneNo authentication headers added
hmac_signatureSigns the payload and adds an X-Webhook-Signature header (see below)
bearer_tokenAdds Authorization: Bearer <token> using the token you configured
basic_authAdds Authorization: Basic <base64(username:password)>
custom_headerAdds a header name/value pair you specify

You can combine HMAC signing with any of the other modes — signing verifies the payload wasn't tampered with, while the auth header lets your endpoint authenticate the caller.

Verify webhook signatures

When HMAC signing is enabled, every webhook request includes an X-Webhook-Signature header containing the hex-encoded HMAC-SHA256 digest of the request body (no sha256= prefix). Verify it to ensure the payload came from Tournament Suite and was not tampered with:

import crypto from 'crypto';

function verifyWebhook(
  payload: string,
  signatureHeader: string,
  secret: string
): boolean {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signatureHeader, 'hex'),
    Buffer.from(expected, 'hex'),
  );
}

app.post('/webhooks/tournamentsuite', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-webhook-signature'] as string;
  if (!sig || !verifyWebhook(req.body.toString(), sig, process.env.WEBHOOK_SECRET!)) {
    return res.sendStatus(401);
  }

  const event = JSON.parse(req.body.toString());
  // process event
  res.sendStatus(200);
});

Event payload format

{
  "id": "dlv_01HXYZ",
  "event": {
    "id": "evt_01HXYZ",
    "type": "tournament.started",
    "timestamp": "2026-07-01T09:00:00.000Z",
    "version": 1
  },
  "data": {
    "aggregateId": "550e8400-e29b-41d4-a716-446655440000",
    "aggregateType": "tournament",
    "payload": {
      "tournamentId": "550e8400-e29b-41d4-a716-446655440000",
      "name": "Summer Open 2026"
    },
    "metadata": {}
  },
  "webhook": {
    "subscriptionId": "sub_01HXYZ",
    "attempt": 1,
    "isRetry": false
  },
  "signature": "<hex-digest>"
}

id identifies this delivery attempt (not the underlying event). The domain event's own ID and type live under event; the actual event data is nested under data.payload, with data.aggregateId / data.aggregateType identifying the entity the event is about. webhook.attempt and webhook.isRetry tell you whether this is the first attempt or a retry.

Available events

Tournament events

EventWhen it fires
tournament.createdA tournament is created
tournament.publishedA tournament is made public
tournament.updatedA tournament's details are changed
tournament.startedA tournament begins
tournament.completedA tournament concludes
tournament.cancelledA tournament is cancelled

Registration events

EventWhen it fires
registration.openedRegistration opens for a tournament
registration.closedRegistration closes for a tournament
registration.fullA tournament reaches its participant cap

Participant events

EventWhen it fires
participant.registeredA player or team registers
participant.approvedA registration is approved
participant.rejectedA registration is rejected
participant.checked_inA participant checks in
participant.eliminatedA participant is eliminated from the bracket
participant.disqualifiedA participant is removed

Match events

EventWhen it fires
match.createdA match is scheduled
match.startedA match begins
match.completedA match result is confirmed
match.disputedA result is disputed
match.rescheduledA match's scheduled time changes
match.forfeitedA match is forfeited by a participant

Circuit events

EventWhen it fires
circuit.createdA circuit is created
circuit.season.startedA circuit season begins
circuit.season.completedA circuit season concludes

Payment events

EventWhen it fires
payment.receivedA payment is received
payout.processedA payout is successfully processed
payout.failedA payout attempt fails

Broadcast events

EventWhen it fires
broadcast.startedA broadcast goes live
broadcast.endedA broadcast ends

User events

EventWhen it fires
user.createdA user account is created
user.verifiedA user completes verification
user.suspendedA user account is suspended

Anti-cheat events

EventWhen it fires
anticheat.detection_raisedAn anti-cheat rule flags a session
anticheat.session_completedAn anti-cheat monitoring session ends

You can also fetch the current catalog of subscribable event types — with descriptions and example payloads — from the GET /webhooks/event-types endpoint rather than hardcoding it.

Delivery configuration

Retry and reliability behavior is per-subscription configuration, not a fixed platform default. When you create a subscription you set:

FieldRangePurpose
timeoutMs1,000–120,000How long Tournament Suite waits for your endpoint to respond before treating the attempt as failed
maxRetries0–10Maximum retry attempts per delivery
retryBaseDelayMs1,000–60,000Delay before the first retry
retryBackoffMultiplier1–10Multiplier applied to the delay on each subsequent retry (exponential backoff)

If your endpoint returns a non-2xx status or does not respond within your configured timeoutMs, Tournament Suite retries using your configured backoff. Deliveries that are still unresolved after 24 hours expire and are no longer retried automatically. You can inspect delivery history — including status, response codes, and timing — under GET /webhooks/subscriptions/:id/deliveries.

Circuit breaker

If a subscription's deliveries keep failing, Tournament Suite can automatically pause it: enabling enableCircuitBreaker on the delivery configuration, along with a failure circuitBreakerThreshold and circuitBreakerTimeoutMs, causes the subscription to open its circuit breaker after repeated failures — pending and new deliveries stop being attempted until the timeout elapses (at which point delivery is tried again to test recovery) or you reset it manually via POST /webhooks/subscriptions/:id/circuit-breaker/reset.

Rate limiting and IP restrictions

A subscription can also set a rateLimitConfig (maximum requests per minute, per hour, per day, and a short burst allowance) to cap how fast Tournament Suite sends it deliveries, and an ipWhitelist of allowed IPs or CIDR ranges used to validate inbound requests associated with the subscription.

Replay and bulk retry

Beyond the passive 24-hour retry window, you can act on failed deliveries directly:

  • POST /webhooks/subscriptions/:id/retry-failed — re-queues every currently failed delivery for the subscription.
  • POST /webhooks/subscriptions/:id/deliveries/:deliveryId/replay — re-queues one specific delivery.

Analytics

Each subscription exposes delivery analytics at GET /webhooks/subscriptions/:id/analytics — success rate, a response-time timeline, and a breakdown of failures by error type — and GET /webhooks/dashboard summarizes totals and health across all of your subscriptions.

Best practices

Always respond with 200 OK immediately, then process the event asynchronously. This avoids timeouts and duplicate retries caused by slow processing.

app.post('/webhooks/tournamentsuite', async (req, res) => {
  res.sendStatus(200); // acknowledge immediately

  // process in the background
  setImmediate(() => handleEvent(req.body));
});

Was this helpful?

Rate Limits

How API rate limits work on Tournament Suite and how to stay within them.

Authentication

How to authenticate requests to the Tournament Suite public Data API using a project-scoped API key.

On this page

Set up a webhook1. Register your endpoint2. Save the signing secret3. Subscribe to eventsAuthenticate outbound requestsVerify webhook signaturesEvent payload formatAvailable eventsTournament eventsRegistration eventsParticipant eventsMatch eventsCircuit eventsPayment eventsBroadcast eventsUser eventsAnti-cheat eventsDelivery configurationCircuit breakerRate limiting and IP restrictionsReplay and bulk retryAnalyticsBest practices