Lucra Sports Webhooks API
Manage webhook configurations to receive real-time event notifications via HTTP POST requests. --- ## Available Event Types | Event | Description | |-------|-------------| | `UserSignedUp` | New user registration | | `UserKYCVerified` | User KYC verification completed | | `FundsDeposited` | User deposited funds | | `C2CWithdrawal` | Convert-to-credit withdrawal initiated | | `TournamentCreated` | Tournament created | | `TournamentCanceled` | Tournament canceled | | `TournamentCompleted` | Tournament completed | | `TournamentCompletionFailed` | Tournament completion failed (async processing error). Payload contains `failure: { code, reason, requestId, attempt, willRetry }`. `willRetry: false` is terminal — subscribe to be notified when a completion request cannot be processed. | | `TournamentComplianceLimitExceeded` | Tournament completion placed on hold due to compliance payout limit | | `TournamentEdited` | Tournament modified | | `TournamentUserJoined` | User joined tournament | | `ScoreIngestionFailed` | Score ingestion failed (async processing error). Payload contains `failure: { code, reason, requestId, attempt, willRetry }`. `willRetry: false` is terminal — subscribe to be notified when a score submission cannot be processed. | | `RecreationalGameCreated` | Recreational game created | | `RecreationalGameJoined` | User joined recreational game | | `RecreationalGameCanceled` | Recreational game canceled | | `RecreationalGameCompleted` | Recreational game completed | | `RecreationalGameStarted` | Recreational game started | | `RecreationalGameCompletionFailed` | Recreational game completion failed (async processing error). Payload contains `failure: { code, reason, requestId, attempt, willRetry }`. `willRetry: false` is terminal — subscribe to be notified when a completion request cannot be processed. | --- ## Event Payloads All events include an `event` field identifying the type. The remaining fields depend on the event. ### Tournament Events Applies to: `TournamentCreated`, `TournamentEdited`, `TournamentCanceled`, `TournamentCompleted`, `TournamentUserJoined`, `TournamentComplianceLimitExceeded` ```json { "event": "TournamentCreated", "tenantId": "YOUR_TENANT_ID", "matchup": { } } ``` The `matchup` object matches the legacy tournament response shape (equivalent to the legacy `GET /api/rest/pool-tournament/{id}`) **Additional fields by event:** | Event | Extra Fields | |-------|-------------| | `TournamentCompleted` | `mode`: `"auto"` | `"manual"` | `"admin"` — how winners were determined | | `TournamentUserJoined` | `newUserId`: UUID of the user who joined; `userMetadata`: their metadata | | `TournamentComplianceLimitExceeded` | `complianceLimits`: compliance threshold details | **Failure events** — `TournamentCompletionFailed`, `ScoreIngestionFailed`: ```json { "event": "TournamentCompletionFailed", "tenantId": "YOUR_TENANT_ID", "entityId": "uuid", "failure": { "code": "COMPLETION_FAILED", "reason": "Human-readable description", "requestId": "uuid", "attempt": 1, "willRetry": true } } ``` `willRetry: false` is terminal — no further delivery attempts will be made. --- ### Recreational Games Events Applies to: `RecreationalGameCreated`, `RecreationalGameJoined`, `RecreationalGameCanceled`, `RecreationalGameCompleted`, `RecreationalGameStarted` ```json { "event": "RecreationalGameCreated", "id": "uuid", "createdByUserId": "uuid", "gameId": "external-game-id", "status": "OPEN", "type": "RECREATIONAL_GAME", "subtype": "GROUP_VS_GROUP", "buyInAmount": 10, "isPublic": true, "winnerGroupId": null, "metadata": null, "groups": [ { "groupId": "uuid", "name": "Team Alpha", "users": [ { "userId": "uuid", "userMetadata": {}, "reward": { "type": "CASH", "value": "20.00", "metadata": null } } ] } ] } ``` `reward.type` is `"CASH"` for buy-in games or `"TENANT_REWARD"` for reward-based games. `RecreationalGameJoined` includes an additional `joinedByUserId` field with the UUID of the user who joined. **Failure events** — `RecreationalGameCompletionFailed`, `ScoreIngestionFailed`: ```json { "event": "RecreationalGameCompletionFailed", "tenantId": "YOUR_TENANT_ID", "entityId": "uuid", "failure": { "code": "COMPLETION_FAILED", "reason": "Human-readable description", "requestId": "uuid", "attempt": 1, "willRetry": true } } ``` `willRetry: false` is terminal — no further delivery attempts will be made. --- ## Configuration Limits - **Maximum 5 webhook configurations** per account - **Single-instance subscriptions**: Some events (e.g., `C2CWithdrawal`) can only exist in one configuration at a time - **Custom headers**: Optional headers added to each webhook request - **Expiration**: Optionally set an expiration date for time-limited webhooks --- ## Request Verification All webhook requests include an `X-Lucra-Signature` header containing an HMAC-SHA256 signature for payload verification. **Signature format:** ``` X-Lucra-Signature: sha256= ``` **Verification steps:** 1. Extract signature from the `X-Lucra-Signature` header 2. Capture the raw request body (before JSON parsing) 3. Compute HMAC-SHA256 of the raw body using your sign secret 4. Compare computed signature with received signature using constant-time comparison **Node.js example:** ```typescript import crypto from 'crypto'; function verifyWebhookSignature( rawBody: Buffer, signature: string, secret: string ): boolean { if (!signature.startsWith('sha256=')) return false; const received = signature.substring(7); const computed = crypto .createHmac('sha256', secret) .update(rawBody) .digest('hex'); if (received.length !== computed.length) return false; return crypto.timingSafeEqual( Buffer.from(received), Buffer.from(computed) ); } ``` --- ## Security Best Practices - **Use raw body** — verify signature against the raw request body before parsing JSON - **Constant-time comparison** — use timing-safe comparison functions to prevent timing attacks - **Secure secret storage** — store sign secrets in environment variables or secret managers - **HTTPS only** — only accept webhooks over HTTPS - **Idempotency** — handle duplicate webhook deliveries gracefully