Webhooks
Receive real-time HTTP notifications when events occur in your tenant. Perfect for updating leaderboards, sending notifications, or syncing data.
Overview
Webhooks are HTTP callbacks that notify your server when specific events occur. Instead of polling the API, you receive push notifications in real-time.
Available Events
| Event | Description |
|---|---|
user.created | A new user registered in your tenant |
user.updated | User profile was updated |
user.deleted | User was deleted |
puzzle.started | A user started a puzzle |
puzzle.completed | A user completed a puzzle |
achievement.unlocked | A user unlocked an achievement |
subscription.created | New subscription started |
subscription.cancelled | Subscription cancelled |
subscription.renewed | Subscription renewed |
Configuration
Configure webhooks via the API or dashboard:
cURL
curl -X POST "https://api.puzzlesection.app/v1/webhooks" \ -H "X-API-Key: ps_live_xxxxxxxxxxxx" \ -H "Content-Type: application/json" \ -d '{ "url": "https://your-server.com/webhooks/puzzle-section", "events": ["puzzle.completed", "achievement.unlocked"] }'Response:
JSON
{ "id": "wh_abc123", "url": "https://your-server.com/webhooks/puzzle-section", "events": ["puzzle.completed", "achievement.unlocked"], "status": "active", "createdAt": "2026-01-09T10:30:00Z"}Payload Format
All webhook payloads follow this structure:
JSON
1{2 "id": "evt_xyz789",3 "type": "puzzle.completed",4 "timestamp": "2026-01-09T14:23:45Z",5 "data": {6 "userId": "usr_abc123",7 "puzzleId": "pzl_sudoku_2026-01-09_medium",8 "puzzleType": "sudoku",9 "difficulty": "medium",10 "timeMs": 423000,11 "score": 8750,12 "rank": 23413 }14}Example: Achievement Unlocked
JSON
1{2 "id": "evt_abc456",3 "type": "achievement.unlocked",4 "timestamp": "2026-01-09T14:23:45Z",5 "data": {6 "userId": "usr_abc123",7 "achievement": {8 "id": "streak_30",9 "name": "Monthly Master",10 "description": "Complete puzzles for 30 consecutive days"11 }12 }13}Signature Verification
Verify webhook signatures to ensure requests are from The Puzzle Section:
Always verify signatures
Never trust webhook payloads without verifying the signature. Attackers could spoof requests to your endpoint.
Each request includes a X-Puzzle-Signature header:
HTTP
POST /webhooks/puzzle-section HTTP/1.1Host: your-server.comContent-Type: application/jsonX-Puzzle-Signature: t=1704806625,v1=5257a869e7ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bdX-Puzzle-Timestamp: 1704806625Verification Example (Node.js)
TypeScript
1import crypto from 'crypto';2
3function verifyWebhookSignature(4 payload: string,5 signature: string,6 secret: string7): boolean {8 const [timestamp, hash] = signature9 .split(',')10 .map(part => part.split('=')[1]);11 12 // Verify timestamp is recent (within 5 minutes)13 const now = Math.floor(Date.now() / 1000);14 if (Math.abs(now - parseInt(timestamp)) > 300) {15 return false; // Replay attack protection16 }17 18 // Compute expected signature19 const signedPayload = `${timestamp}.${payload}`;20 const expectedHash = crypto21 .createHmac('sha256', secret)22 .update(signedPayload)23 .digest('hex');24 25 // Constant-time comparison26 return crypto.timingSafeEqual(27 Buffer.from(hash),28 Buffer.from(expectedHash)29 );30}31
32// Usage in Express33app.post('/webhooks/puzzle-section', (req, res) => {34 const signature = req.headers['x-puzzle-signature'] as string;35 const payload = JSON.stringify(req.body);36 37 if (!verifyWebhookSignature(payload, signature, WEBHOOK_SECRET)) {38 return res.status(401).json({ error: 'Invalid signature' });39 }40 41 // Process the webhook...42 res.status(200).json({ received: true });43});Best Practices
- Respond quickly — Return a 2xx response within 5 seconds. Process events asynchronously if needed.
- Handle retries — We retry failed deliveries with exponential backoff. Use the event ID for idempotency.
- Verify signatures — Always verify the
X-Puzzle-Signatureheader. - Use HTTPS — Webhook endpoints must use HTTPS for security.
- Log events — Store webhook events for debugging and audit trails.
Retry Policy
Failed webhook deliveries are retried with exponential backoff:
| Attempt | Delay |
|---|---|
| 1st retry | 1 minute |
| 2nd retry | 5 minutes |
| 3rd retry | 30 minutes |
| 4th retry | 2 hours |
| 5th retry (final) | 24 hours |
After 5 failed attempts, the webhook is marked as failed and you'll receive an email notification.