# Bot Integration Guide This guide explains how to integrate your bot with the Jackbox Game Picker API for live voting and game notifications. ## Table of Contents 1. [Live Voting (Bot → API)](#live-voting-bot--api) 2. [Game Notifications (API → Bot)](#game-notifications-api--bot) 3. [Webhook Management](#webhook-management) 4. [Testing](#testing) --- ## Live Voting (Bot → API) Your bot can send real-time votes to the API when it detects "thisgame++" or "thisgame--" in Kosmi chat. ### Endpoint ``` POST /api/votes/live ``` ### Authentication Requires JWT token in Authorization header: ``` Authorization: Bearer YOUR_JWT_TOKEN ``` ### Request Body ```json { "username": "string", // Username of the voter "vote": "up" | "down", // "up" for thisgame++, "down" for thisgame-- "timestamp": "string" // ISO 8601 timestamp (e.g., "2025-11-01T20:30:00Z") } ``` ### Response (Success) ```json { "success": true, "message": "Vote recorded successfully", "session": { "id": 123, "games_played": 5 }, "game": { "id": 45, "title": "Fibbage 4", "upvotes": 46, "downvotes": 3, "popularity_score": 43 }, "vote": { "username": "TestUser", "type": "up", "timestamp": "2025-11-01T20:30:00Z" } } ``` ### Error Responses - **400 Bad Request**: Invalid payload or timestamp format - **404 Not Found**: No active session or timestamp doesn't match any game - **409 Conflict**: Duplicate vote (within 1 second of previous vote from same user) - **500 Internal Server Error**: Server error ### Example Implementation (Node.js) ```javascript // When bot detects "thisgame++" or "thisgame--" in Kosmi chat async function handleVote(username, message) { const isUpvote = message.includes('thisgame++'); const isDownvote = message.includes('thisgame--'); if (!isUpvote && !isDownvote) return; try { const response = await fetch('http://your-api-url/api/votes/live', { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.JWT_TOKEN}`, 'Content-Type': 'application/json' }, body: JSON.stringify({ username: username, vote: isUpvote ? 'up' : 'down', timestamp: new Date().toISOString() }) }); const data = await response.json(); if (response.ok) { console.log(`Vote recorded for ${data.game.title}: ${data.game.upvotes}👍 ${data.game.downvotes}👎`); } else { console.error('Vote failed:', data.error); } } catch (error) { console.error('Error sending vote:', error); } } ``` ### Important Notes - **Deduplication**: Votes from the same user within 1 second are automatically rejected to prevent spam - **Timestamp Matching**: The API matches the vote timestamp to the correct game based on when games were played - **Active Session Required**: Votes can only be recorded when there's an active session with games played --- ## Game Notifications (API → Bot) The API can send webhooks to your bot when games are added to a session, allowing you to announce "Coming up next: Game Title!" in Kosmi chat. ### Webhook Event: `game.added` Triggered whenever a game is added to an active session (either via picker or manual selection). ### Webhook Payload ```json { "event": "game.added", "timestamp": "2025-11-01T20:30:00Z", "data": { "session": { "id": 123, "is_active": true, "games_played": 5 }, "game": { "id": 45, "title": "Fibbage 4", "pack_name": "The Jackbox Party Pack 9", "min_players": 2, "max_players": 8, "manually_added": false } } } ``` ### Webhook Headers The API sends the following headers with each webhook: - `Content-Type: application/json` - `X-Webhook-Signature: sha256=` - HMAC-SHA256 signature for verification - `X-Webhook-Event: game.added` - Event type - `User-Agent: Jackbox-Game-Picker-Webhook/1.0` ### Signature Verification **IMPORTANT**: Always verify the webhook signature to ensure the request is authentic. ```javascript const crypto = require('crypto'); function verifyWebhookSignature(signature, payload, secret) { if (!signature || !signature.startsWith('sha256=')) { return false; } const expectedSignature = 'sha256=' + crypto .createHmac('sha256', secret) .update(JSON.stringify(payload)) .digest('hex'); // Use timing-safe comparison try { return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expectedSignature) ); } catch (err) { return false; } } ``` ### Example Implementation (Express.js) ```javascript const express = require('express'); const crypto = require('crypto'); const app = express(); // IMPORTANT: Use express.json() with verify option to get raw body app.use(express.json({ verify: (req, res, buf) => { req.rawBody = buf.toString('utf8'); } })); app.post('/webhook/jackbox', (req, res) => { const signature = req.headers['x-webhook-signature']; const secret = process.env.WEBHOOK_SECRET; // Your webhook secret // Verify signature if (!signature || !signature.startsWith('sha256=')) { return res.status(401).send('Missing or invalid signature'); } const expectedSignature = 'sha256=' + crypto .createHmac('sha256', secret) .update(req.rawBody) .digest('hex'); // Timing-safe comparison try { if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expectedSignature))) { return res.status(401).send('Invalid signature'); } } catch (err) { return res.status(401).send('Invalid signature'); } // Handle the event if (req.body.event === 'game.added') { const game = req.body.data.game; // Send message to Kosmi chat sendKosmiMessage(`🎮 Coming up next: ${game.title}!`); console.log(`Announced game: ${game.title} from ${game.pack_name}`); } // Always respond with 200 OK res.status(200).send('OK'); }); function sendKosmiMessage(message) { // Your Kosmi chat integration here console.log('Sending to Kosmi:', message); } app.listen(3001, () => { console.log('Webhook receiver listening on port 3001'); }); ``` --- ## Webhook Management You can manage webhooks through the API using the following endpoints (all require JWT authentication). ### List All Webhooks ```bash GET /api/webhooks Authorization: Bearer YOUR_JWT_TOKEN ``` ### Create Webhook ```bash POST /api/webhooks Authorization: Bearer YOUR_JWT_TOKEN Content-Type: application/json { "name": "Kosmi Bot", "url": "http://your-bot-url/webhook/jackbox", "secret": "your_shared_secret_key", "events": ["game.added"] } ``` ### Update Webhook ```bash PATCH /api/webhooks/:id Authorization: Bearer YOUR_JWT_TOKEN Content-Type: application/json { "enabled": false // Disable webhook } ``` ### Delete Webhook ```bash DELETE /api/webhooks/:id Authorization: Bearer YOUR_JWT_TOKEN ``` ### Test Webhook ```bash POST /api/webhooks/test/:id Authorization: Bearer YOUR_JWT_TOKEN ``` Sends a test `game.added` event to verify your webhook is working. ### View Webhook Logs ```bash GET /api/webhooks/:id/logs?limit=50 Authorization: Bearer YOUR_JWT_TOKEN ``` Returns recent webhook delivery attempts with status codes and errors. --- ## Testing ### Test Live Voting ```bash # Get your JWT token first curl -X POST "http://localhost:5000/api/auth/login" \ -H "Content-Type: application/json" \ -d '{"apiKey": "YOUR_API_KEY"}' # Send a test vote curl -X POST "http://localhost:5000/api/votes/live" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "username": "TestUser", "vote": "up", "timestamp": "2025-11-01T20:30:00Z" }' ``` ### Test Webhooks ```bash # Create a webhook curl -X POST "http://localhost:5000/api/webhooks" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "name": "Test Webhook", "url": "http://localhost:3001/webhook/jackbox", "secret": "test_secret_123", "events": ["game.added"] }' # Test the webhook curl -X POST "http://localhost:5000/api/webhooks/test/1" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" # Check webhook logs curl -X GET "http://localhost:5000/api/webhooks/1/logs" \ -H "Authorization: Bearer YOUR_JWT_TOKEN" ``` --- ## Available Events Currently supported webhook events: - `game.added` - Triggered when a game is added to an active session More events may be added in the future (e.g., `session.started`, `session.ended`, `vote.recorded`). --- ## Security Best Practices 1. **Always verify webhook signatures** - Never trust webhook payloads without verification 2. **Use HTTPS in production** - Webhook URLs should use HTTPS to prevent man-in-the-middle attacks 3. **Keep secrets secure** - Store webhook secrets in environment variables, never in code 4. **Implement rate limiting** - Protect your webhook endpoints from abuse 5. **Log webhook activity** - Keep logs of webhook deliveries for debugging 6. **Use strong secrets** - Generate cryptographically secure random strings for webhook secrets --- ## Troubleshooting ### Votes Not Being Recorded - Check that there's an active session with games played - Verify the timestamp is within the timeframe of a played game - Ensure you're not sending duplicate votes within 1 second - Check API logs for error messages ### Webhooks Not Being Received - Verify your webhook URL is publicly accessible - Check webhook logs via `/api/webhooks/:id/logs` - Test with `ngrok` or similar tool if developing locally - Ensure your webhook endpoint responds with 200 OK - Check that webhook is enabled in the database ### Signature Verification Failing - Ensure you're using the raw request body for signature verification - Check that the secret matches what's stored in the database - Verify you're using HMAC-SHA256 algorithm - Make sure to prefix with "sha256=" when comparing --- ## Support For issues or questions, contact: cottongin@cottongin.xyz