Update README.md, docs/api/README.md, session-lifecycle guide, webhooks-and-events guide, and archive docs (BOT_INTEGRATION, API_QUICK_REFERENCE) with new endpoints and vote.received event. Made-with: Cursor
9.0 KiB
API Quick Reference
Quick reference for Live Voting, WebSocket, and Webhook endpoints.
Base URL
http://localhost:5000/api
Authentication
All endpoints require JWT authentication:
Authorization: Bearer YOUR_JWT_TOKEN
Get token via:
POST /api/auth/login
Body: { "key": "YOUR_ADMIN_KEY" }
WebSocket Events
Connect to WebSocket
ws://localhost:5000/api/sessions/live
Message Protocol:
// Authenticate
{ "type": "auth", "token": "YOUR_JWT_TOKEN" }
// Subscribe to session
{ "type": "subscribe", "sessionId": 123 }
// Unsubscribe
{ "type": "unsubscribe", "sessionId": 123 }
// Heartbeat
{ "type": "ping" }
Server Events:
// Auth success
{ "type": "auth_success", "message": "..." }
// Subscribed
{ "type": "subscribed", "sessionId": 123 }
// Session started
{
"type": "session.started",
"timestamp": "2025-11-01T...",
"data": {
"session": { "id": 123, "is_active": 1, "created_at": "...", "notes": "..." }
}
}
// Game added
{
"type": "game.added",
"timestamp": "2025-11-01T...",
"data": {
"session": { "id": 123, "is_active": true, "games_played": 5 },
"game": { "id": 45, "title": "Fibbage 4", ... }
}
}
// Session ended
{
"type": "session.ended",
"timestamp": "2025-11-01T...",
"data": {
"session": { "id": 123, "is_active": 0, "games_played": 5 }
}
}
// Vote received (live votes only, not chat-import)
{
"type": "vote.received",
"timestamp": "2025-11-01T...",
"data": {
"sessionId": 123,
"game": { "id": 42, "title": "Quiplash 3", "pack_name": "Party Pack 7" },
"vote": { "username": "viewer123", "type": "up", "timestamp": "2025-11-01T20:30:00Z" },
"totals": { "upvotes": 14, "downvotes": 3, "popularity_score": 11 }
}
}
// Pong
{ "type": "pong" }
Votes
Get Vote History
GET /api/votes?session_id=5&game_id=42&username=viewer123&vote_type=up&page=1&limit=50
Response (200):
{
"votes": [
{
"id": 891,
"session_id": 5,
"game_id": 42,
"game_title": "Quiplash 3",
"pack_name": "Party Pack 7",
"username": "viewer123",
"vote_type": "up",
"timestamp": "2025-11-01T20:30:00.000Z",
"created_at": "2025-11-01T20:30:01.000Z"
}
],
"pagination": { "page": 1, "limit": 50, "total": 237, "total_pages": 5 }
}
All query parameters are optional. No authentication required.
Submit Live Vote
POST /api/votes/live
Request Body:
{
"username": "string",
"vote": "up" | "down",
"timestamp": "2025-11-01T20:30:00Z"
}
Success Response (200):
{
"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- Invalid payload404- No active session or timestamp doesn't match any game409- Duplicate vote (within 1 second)
Webhooks
List Webhooks
GET /api/webhooks
Response:
[
{
"id": 1,
"name": "Kosmi Bot",
"url": "http://bot-url/webhook",
"events": ["game.added"],
"enabled": true,
"created_at": "2025-11-01T20:00:00Z"
}
]
Get Single Webhook
GET /api/webhooks/:id
Create Webhook
POST /api/webhooks
Request Body:
{
"name": "Kosmi Bot",
"url": "http://bot-url/webhook",
"secret": "your_shared_secret",
"events": ["game.added"]
}
Response (201):
{
"id": 1,
"name": "Kosmi Bot",
"url": "http://bot-url/webhook",
"events": ["game.added"],
"enabled": true,
"created_at": "2025-11-01T20:00:00Z",
"message": "Webhook created successfully"
}
Update Webhook
PATCH /api/webhooks/:id
Request Body (all fields optional):
{
"name": "Updated Name",
"url": "http://new-url/webhook",
"secret": "new_secret",
"events": ["game.added"],
"enabled": false
}
Delete Webhook
DELETE /api/webhooks/:id
Response (200):
{
"message": "Webhook deleted successfully",
"webhookId": 1
}
Test Webhook
POST /api/webhooks/test/:id
Sends a test game.added event to verify webhook is working.
Response (200):
{
"message": "Test webhook sent",
"note": "Check webhook_logs table for delivery status"
}
Get Webhook Logs
GET /api/webhooks/:id/logs?limit=50
Response:
[
{
"id": 1,
"webhook_id": 1,
"event_type": "game.added",
"payload": { /* full payload */ },
"response_status": 200,
"error_message": null,
"created_at": "2025-11-01T20:30:00Z"
}
]
Webhook Payloads
Event: session.started
Sent when a new session is created.
Headers:
Content-Type: application/jsonX-Webhook-Signature: sha256=<hmac_signature>X-Webhook-Event: session.startedUser-Agent: Jackbox-Game-Picker-Webhook/1.0
Payload:
{
"event": "session.started",
"timestamp": "2025-11-01T20:00:00Z",
"data": {
"session": {
"id": 123,
"is_active": 1,
"created_at": "2025-11-01T20:00:00Z",
"notes": "Friday Game Night"
}
}
}
Event: game.added
Sent when a game is added to an active session.
Headers:
Content-Type: application/jsonX-Webhook-Signature: sha256=<hmac_signature>X-Webhook-Event: game.addedUser-Agent: Jackbox-Game-Picker-Webhook/1.0
Payload:
{
"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
}
}
}
Event: session.ended
Sent when a session is closed/ended.
Headers:
Content-Type: application/jsonX-Webhook-Signature: sha256=<hmac_signature>X-Webhook-Event: session.endedUser-Agent: Jackbox-Game-Picker-Webhook/1.0
Payload:
{
"event": "session.ended",
"timestamp": "2025-11-01T20:30:00Z",
"data": {
"session": {
"id": 123,
"is_active": 0,
"games_played": 5
}
}
}
cURL Examples
Submit 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"
}'
Create Webhook
curl -X POST "http://localhost:5000/api/webhooks" \
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"name": "Kosmi Bot",
"url": "http://localhost:3001/webhook/jackbox",
"secret": "test_secret_123",
"events": ["game.added"]
}'
Test Webhook
curl -X POST "http://localhost:5000/api/webhooks/test/1" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
View Webhook Logs
curl -X GET "http://localhost:5000/api/webhooks/1/logs?limit=10" \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
Webhook Signature Verification
Node.js Example:
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');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expectedSignature)
);
}
// In your webhook endpoint:
app.post('/webhook/jackbox', (req, res) => {
const signature = req.headers['x-webhook-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!verifyWebhookSignature(signature, req.body, secret)) {
return res.status(401).send('Invalid signature');
}
// Process webhook...
res.status(200).send('OK');
});
Error Codes
| Code | Meaning |
|---|---|
| 200 | Success |
| 201 | Created |
| 400 | Bad Request - Invalid payload |
| 401 | Unauthorized - Invalid JWT or signature |
| 404 | Not Found - Resource doesn't exist |
| 409 | Conflict - Duplicate vote |
| 500 | Internal Server Error |
Rate Limiting
Currently no rate limiting is implemented. Consider implementing rate limiting in production:
- Per IP address
- Per JWT token
- Per webhook endpoint
Best Practices
- Always verify webhook signatures before processing
- Use HTTPS for webhook URLs in production
- Store secrets securely in environment variables
- Respond quickly to webhooks (< 5 seconds)
- Log webhook activity for debugging
- Handle retries gracefully if implementing retry logic
- Validate timestamps to prevent replay attacks
For detailed documentation, see BOT_INTEGRATION.md