# Webhooks and Events A narrative guide to the Jackbox Game Picker event notification system: webhooks (HTTP callbacks) and WebSocket (persistent real-time connections). Both deliver event data about session and game activity. --- ## 1. Two Notification Systems The API offers two complementary ways to receive event notifications: | System | Model | Best for | |--------|-------|----------| | **Webhooks** | HTTP POST callbacks to your URL | Server-to-server, external integrations | | **WebSocket** | Persistent bidirectional connection | Real-time UIs, dashboards, live tools | Both systems emit the same kinds of events (e.g. `game.added`) but differ in how they deliver them. --- ## 2. When to Use Which ### Use Webhooks when: - **Server-to-server** — Discord bots, Slack, logging pipelines, external APIs - **Stateless** — Your endpoint receives a POST, processes it, and returns. No long-lived connection - **Behind firewalls** — Your server can receive HTTP but may not hold open WebSocket connections - **Async delivery** — You’re fine with HTTP round-trip latency and want delivery logged and auditable ### Use WebSocket when: - **Real-time UI** — Dashboards, admin panels, live session viewers - **Instant updates** — You need push-style notifications with minimal latency - **Persistent connection** — Your app keeps a live connection and subscribes to specific sessions - **Best-effort is fine** — WebSocket is push-only; there’s no built-in delivery log for events --- ## 3. Webhook Setup Webhooks are registered via the REST API. See [Webhooks endpoints](../endpoints/webhooks.md) for full CRUD details. ### Create a Webhook `POST /api/webhooks` with: - `name` — Display name (e.g. `"Discord Bot"`) - `url` — Callback URL (must be a valid HTTP/HTTPS URL) - `secret` — Shared secret for signing payloads (HMAC-SHA256) - `events` — Array of event types that trigger this webhook (e.g. `["game.added"]`) **Example:** ```bash curl -X POST "http://localhost:5000/api/webhooks" \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Discord Bot", "url": "https://my-server.com/webhooks/jackbox", "secret": "mysecret123", "events": ["game.added"] }' ``` The `events` array defines which events fire this webhook. Currently, the codebase triggers webhooks for **`game.added`** when a game is added to a session. The `triggerWebhook` function in `backend/utils/webhooks.js` is invoked from `sessions.js` on that event. ### Update, Enable/Disable, Delete - **Update:** `PATCH /api/webhooks/{id}` — Change `name`, `url`, `secret`, `events`, or `enabled` - **Disable:** `PATCH /api/webhooks/{id}` with `"enabled": false` — Stops delivery without deleting config - **Delete:** `DELETE /api/webhooks/{id}` — Removes webhook and its logs --- ## 4. Webhook Delivery ### How it works When an event occurs (e.g. a game is added), the server: 1. Finds all enabled webhooks subscribed to that event 2. Sends an async HTTP POST to each webhook URL 3. Logs each delivery attempt in `webhook_logs` (status, error, payload) ### Payload format Each POST body is JSON: ```json { "event": "game.added", "timestamp": "2026-03-15T20:30:00.000Z", "data": { "session": { "id": 3, "is_active": true, "games_played": 2 }, "game": { "id": 42, "title": "Quiplash 3", "pack_name": "Jackbox Party Pack 7", "min_players": 3, "max_players": 8, "manually_added": false } } } ``` Headers include: - `Content-Type: application/json` - `X-Webhook-Event: game.added` - `X-Webhook-Signature: sha256=` — Use your `secret` to verify the payload ### View delivery logs `GET /api/webhooks/{id}/logs` returns recent delivery attempts (status, error message, payload). ### Test a webhook `POST /api/webhooks/test/{id}` sends a dummy `game.added` event to the webhook URL. Delivery runs asynchronously; check logs for status. --- ## 5. WebSocket Events The WebSocket server runs at `/api/sessions/live` on the same host and port as the HTTP API. See [WebSocket protocol](../websocket.md) for connection, authentication, and subscription details. ### Event types and audience | Event | Broadcast to | Triggered by | |-------|--------------|--------------| | `session.started` | All authenticated clients | `POST /api/sessions` | | `game.added` | Session subscribers | `POST /api/sessions/{id}/games` | | `session.ended` | Session subscribers | `POST /api/sessions/{id}/close` | | `player-count.updated` | Session subscribers | `PATCH /api/sessions/{sessionId}/games/{sessionGameId}/player-count` | | `vote.received` | Session subscribers | `POST /api/votes/live` (live votes only, not chat-import) | `session.started` goes to every authenticated client. The others go only to clients that have subscribed to the relevant session via `{ "type": "subscribe", "sessionId": 3 }`. ### Envelope format All events use this envelope: ```json { "type": "", "timestamp": "2026-03-15T20:30:00.000Z", "data": { ... } } ``` `data` contains event-specific fields (session, game, player count, etc.) as described in [WebSocket protocol](../websocket.md). --- ## 6. Comparison | Feature | Webhooks | WebSocket | |---------|----------|-----------| | **Connection** | Stateless HTTP | Persistent | | **Auth** | Secret in config | JWT per connection | | **Events** | `game.added` | `session.started`, `game.added`, `session.ended`, `player-count.updated`, `vote.received` | | **Latency** | Higher (HTTP round trip) | Lower (push) | | **Reliability** | Logged, auditable | Best-effort | --- ## 7. Example: Discord Bot Use a webhook to post game additions to a Discord channel. You’ll need: 1. A webhook created in the Game Picker API pointing to your server 2. A small server that receives the webhook and forwards to Discord’s Incoming Webhook **Webhook receiver (Node.js):** ```javascript const crypto = require('crypto'); app.post('/webhooks/jackbox', express.json(), (req, res) => { const signature = req.headers['x-webhook-signature']; const payload = JSON.stringify(req.body); // Verify HMAC-SHA256 using your webhook secret const expected = 'sha256=' + crypto .createHmac('sha256', process.env.WEBHOOK_SECRET) .update(payload) .digest('hex'); if (signature !== expected) { return res.status(401).send('Invalid signature'); } if (req.body.event === 'game.added') { const { session, game } = req.body.data; const discordPayload = { content: `🎮 **${game.title}** added to session #${session.id} (${game.min_players}-${game.max_players} players)` }; fetch(process.env.DISCORD_WEBHOOK_URL, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(discordPayload) }).catch(err => console.error('Discord post failed:', err)); } res.status(200).send('OK'); }); ``` Register the Game Picker webhook with your server’s URL (e.g. `https://my-bot.example.com/webhooks/jackbox`), set `events` to `["game.added"]`, and use the same `secret` in your server’s `WEBHOOK_SECRET`. --- ## Cross-references - **[Webhooks endpoints](../endpoints/webhooks.md)** — Full CRUD, request/response schemas, errors - **[WebSocket protocol](../websocket.md)** — Connection, auth, subscriptions, event payloads