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
515 lines
9.0 KiB
Markdown
515 lines
9.0 KiB
Markdown
# 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:
|
|
```bash
|
|
POST /api/auth/login
|
|
Body: { "key": "YOUR_ADMIN_KEY" }
|
|
```
|
|
|
|
---
|
|
|
|
## WebSocket Events
|
|
|
|
### Connect to WebSocket
|
|
|
|
```
|
|
ws://localhost:5000/api/sessions/live
|
|
```
|
|
|
|
**Message Protocol**:
|
|
|
|
```json
|
|
// Authenticate
|
|
{ "type": "auth", "token": "YOUR_JWT_TOKEN" }
|
|
|
|
// Subscribe to session
|
|
{ "type": "subscribe", "sessionId": 123 }
|
|
|
|
// Unsubscribe
|
|
{ "type": "unsubscribe", "sessionId": 123 }
|
|
|
|
// Heartbeat
|
|
{ "type": "ping" }
|
|
```
|
|
|
|
**Server Events**:
|
|
|
|
```json
|
|
// 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
|
|
|
|
```http
|
|
GET /api/votes?session_id=5&game_id=42&username=viewer123&vote_type=up&page=1&limit=50
|
|
```
|
|
|
|
**Response (200)**:
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```http
|
|
POST /api/votes/live
|
|
```
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"username": "string",
|
|
"vote": "up" | "down",
|
|
"timestamp": "2025-11-01T20:30:00Z"
|
|
}
|
|
```
|
|
|
|
**Success Response (200)**:
|
|
```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` - Invalid payload
|
|
- `404` - No active session or timestamp doesn't match any game
|
|
- `409` - Duplicate vote (within 1 second)
|
|
|
|
---
|
|
|
|
## Webhooks
|
|
|
|
### List Webhooks
|
|
|
|
```http
|
|
GET /api/webhooks
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
[
|
|
{
|
|
"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
|
|
|
|
```http
|
|
GET /api/webhooks/:id
|
|
```
|
|
|
|
### Create Webhook
|
|
|
|
```http
|
|
POST /api/webhooks
|
|
```
|
|
|
|
**Request Body**:
|
|
```json
|
|
{
|
|
"name": "Kosmi Bot",
|
|
"url": "http://bot-url/webhook",
|
|
"secret": "your_shared_secret",
|
|
"events": ["game.added"]
|
|
}
|
|
```
|
|
|
|
**Response (201)**:
|
|
```json
|
|
{
|
|
"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
|
|
|
|
```http
|
|
PATCH /api/webhooks/:id
|
|
```
|
|
|
|
**Request Body** (all fields optional):
|
|
```json
|
|
{
|
|
"name": "Updated Name",
|
|
"url": "http://new-url/webhook",
|
|
"secret": "new_secret",
|
|
"events": ["game.added"],
|
|
"enabled": false
|
|
}
|
|
```
|
|
|
|
### Delete Webhook
|
|
|
|
```http
|
|
DELETE /api/webhooks/:id
|
|
```
|
|
|
|
**Response (200)**:
|
|
```json
|
|
{
|
|
"message": "Webhook deleted successfully",
|
|
"webhookId": 1
|
|
}
|
|
```
|
|
|
|
### Test Webhook
|
|
|
|
```http
|
|
POST /api/webhooks/test/:id
|
|
```
|
|
|
|
Sends a test `game.added` event to verify webhook is working.
|
|
|
|
**Response (200)**:
|
|
```json
|
|
{
|
|
"message": "Test webhook sent",
|
|
"note": "Check webhook_logs table for delivery status"
|
|
}
|
|
```
|
|
|
|
### Get Webhook Logs
|
|
|
|
```http
|
|
GET /api/webhooks/:id/logs?limit=50
|
|
```
|
|
|
|
**Response**:
|
|
```json
|
|
[
|
|
{
|
|
"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/json`
|
|
- `X-Webhook-Signature: sha256=<hmac_signature>`
|
|
- `X-Webhook-Event: session.started`
|
|
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
|
|
|
|
**Payload**:
|
|
```json
|
|
{
|
|
"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/json`
|
|
- `X-Webhook-Signature: sha256=<hmac_signature>`
|
|
- `X-Webhook-Event: game.added`
|
|
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
|
|
|
|
**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
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Event: `session.ended`
|
|
|
|
Sent when a session is closed/ended.
|
|
|
|
**Headers**:
|
|
- `Content-Type: application/json`
|
|
- `X-Webhook-Signature: sha256=<hmac_signature>`
|
|
- `X-Webhook-Event: session.ended`
|
|
- `User-Agent: Jackbox-Game-Picker-Webhook/1.0`
|
|
|
|
**Payload**:
|
|
```json
|
|
{
|
|
"event": "session.ended",
|
|
"timestamp": "2025-11-01T20:30:00Z",
|
|
"data": {
|
|
"session": {
|
|
"id": 123,
|
|
"is_active": 0,
|
|
"games_played": 5
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## cURL Examples
|
|
|
|
### Submit Vote
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
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
|
|
|
|
```bash
|
|
curl -X POST "http://localhost:5000/api/webhooks/test/1" \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
```
|
|
|
|
### View Webhook Logs
|
|
|
|
```bash
|
|
curl -X GET "http://localhost:5000/api/webhooks/1/logs?limit=10" \
|
|
-H "Authorization: Bearer YOUR_JWT_TOKEN"
|
|
```
|
|
|
|
---
|
|
|
|
## Webhook Signature Verification
|
|
|
|
**Node.js Example**:
|
|
|
|
```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');
|
|
|
|
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
|
|
|
|
1. **Always verify webhook signatures** before processing
|
|
2. **Use HTTPS** for webhook URLs in production
|
|
3. **Store secrets securely** in environment variables
|
|
4. **Respond quickly** to webhooks (< 5 seconds)
|
|
5. **Log webhook activity** for debugging
|
|
6. **Handle retries gracefully** if implementing retry logic
|
|
7. **Validate timestamps** to prevent replay attacks
|
|
|
|
---
|
|
|
|
For detailed documentation, see [BOT_INTEGRATION.md](BOT_INTEGRATION.md)
|
|
|