Webhooks

Subscribe to platform events. We POST a signed JSON payload to your URL on every match.

How it works

  1. You register a subscription: a URL, a signing secret, and a list of events.
  2. When a matching event happens (e.g. a player opens a lootbox), we enqueue a delivery in the same DB transaction as the business write — so we never lose events on success.
  3. A background worker picks up pending deliveries, signs them HMAC-SHA256, and POSTs to your URL.
  4. Your endpoint replies 2xx to acknowledge. 4xx (non-retryable) marks failed; 5xx / network → retried with exponential backoff.

Subscription shape

FieldTypeNotes
urlstringHTTPS endpoint. We send a single POST with JSON body.
signing_secretstringShared symmetric secret. Used for HMAC.
eventsstring[]Exact event types, prefix wildcards ("lootbox.*"), or global ("*").
statusenumactive | paused | failed

Event catalog

Event typeFires whenData fields
lootbox.openedA player opens a lootbox (any source)lootbox_id, lootbox_name, user, open_id, reward, server_seed_hash, opened_at
raffle.drawnA raffle draw completesraffle_id, winner_username, prize_winz, total_tickets, server_seed_hash
jackpot.droppedA jackpot pool is paid outjackpot_id, winner_username, amount, pool_before, pool_after, trigger_reason
mini_game.playedA mini-game outcome resolvesmini_game_id, user, outcome, server_seed_hash
tournament.closedA tournament closes + prizes are paidtournament_id, payouts[], total_paid
journey.completedA player completes a lifecycle journeyjourney_id, journey_name, user, reward_winz_granted
bonus.grantedA bonus is issued to a playerbonus_id, template_name, type, amount, user
bonus.convertedA bonus wagering target is hit and converts to realbonus_id, user, real_credited
kyc.decisionKYC document is approved or rejecteddocument_id, user, decision, reason
player.status_changedLifecycle status changesuser, from, to, reason

Payload shape

{
  "id":           "…delivery uuid…",
  "type":         "lootbox.opened",
  "delivered_at": "2025-05-19T20:14:33Z",
  "data": {
    "lootbox_id":   "…",
    "user":         "StarlordV7",
    "reward":       { "asset": "WINZ", "amount": 1500, "segment_label": "big" }
  }
}

Signature verification

Each delivery carries two headers:

X-Wowsino-Signature: t=<unixSeconds>,v1=<hex>
X-Wowsino-Event:     <event_type>

Compute HMAC-SHA256(signing_secret, "<t>.<rawBody>") and constant-time compare against v1. Reject deliveries where |now − t| > 300s (replay window).

Node.js verification

import { createHmac, timingSafeEqual } from 'node:crypto';

function verify(secret, header, rawBody) {
  const m = /^t=(\d+),v1=([0-9a-f]+)$/.exec(header ?? '');
  if (!m) return false;
  if (Math.abs(Date.now()/1000 - Number(m[1])) > 300) return false;
  const expected = createHmac('sha256', secret)
    .update(`${m[1]}.${rawBody}`)
    .digest('hex');
  const a = Buffer.from(m[2], 'hex');
  const b = Buffer.from(expected, 'hex');
  return a.length === b.length && timingSafeEqual(a, b);
}

Retry policy

A subscription that fails many times in a row may auto-pause; check failure_count on the subscription.

Same-tx enqueue: we write the delivery row inside the business transaction that produced the event. If your business write succeeded, the webhook will at least be enqueued.