WebSockets
VentureKit supports real-time WebSocket APIs alongside REST APIs in the same project, with built-in connection management via DynamoDB.
Enabling WebSocket
Section titled “Enabling WebSocket”Add WebSocket configuration to your environment config:
import type { EnvConfigInput } from '@venturekit/core';
export const dev: EnvConfigInput = { preset: 'nano', websocket: { enabled: true, routeSelectionExpression: '$request.body.action', idleTimeoutSec: 600, // 10 minutes (max: 7200) throttleRateLimit: 100, throttleBurstLimit: 200, },};Two-Phase Authentication
Section titled “Two-Phase Authentication”VentureKit uses two-phase authentication for WebSockets. JWTs are never sent in query parameters (which appear in logs), but instead over the already-encrypted WebSocket channel:
1. Client connects: wss://api.example.com (no credentials in URL)2. Server saves connection as unauthenticated (30s TTL)3. Client sends: { "action": "auth", "token": "<jwt>" }4. Server verifies JWT, upgrades connection (TTL extended)5. Server responds: { "action": "auth", "success": true }If the client doesn’t authenticate within 30 seconds, the connection record expires automatically.
WebSocket Handlers
Section titled “WebSocket Handlers”Create three handler files in src/ws/:
| Route | File | Purpose |
|---|---|---|
$connect | src/ws/connect.ts | Save unauthenticated connection |
$disconnect | src/ws/disconnect.ts | Remove connection record |
$default | src/ws/default.ts | Handle auth, messages, broadcast |
Connect Handler
Section titled “Connect Handler”import { connectionStore } from '@venturekit/runtime';
export const handler = async (event: any) => { const connectionId = event.requestContext.connectionId; await connectionStore.save(connectionId); return { statusCode: 200 };};Disconnect Handler
Section titled “Disconnect Handler”import { connectionStore } from '@venturekit/runtime';
export const handler = async (event: any) => { const connectionId = event.requestContext.connectionId; await connectionStore.remove(connectionId); return { statusCode: 200 };};Default Handler
Section titled “Default Handler”import { connectionStore } from '@venturekit/runtime';
export const handler = async (event: any) => { const connectionId = event.requestContext.connectionId; const body = JSON.parse(event.body); const { domainName, stage } = event.requestContext;
switch (body.action) { case 'auth': const user = await verifyJwt(body.token); await connectionStore.authenticate(connectionId, { userId: user.sub, email: user.email, tenantId: body.tenantId, }); await connectionStore.postToConnection(domainName, stage, connectionId, { action: 'auth', success: true, userId: user.sub, }); break;
case 'ping': await connectionStore.postToConnection(domainName, stage, connectionId, { action: 'pong', timestamp: new Date().toISOString(), }); break;
case 'broadcast': await connectionStore.broadcast(domainName, stage, body.data); break;
case 'send': await connectionStore.sendToUser(domainName, stage, body.to, body.data); break; }
return { statusCode: 200 };};Connection Store API
Section titled “Connection Store API”All connection management is in @venturekit/runtime:
import { connectionStore } from '@venturekit/runtime';
// Lifecycleawait connectionStore.save(connectionId);await connectionStore.authenticate(connectionId, { userId, email, tenantId });await connectionStore.remove(connectionId);
// Messagingawait connectionStore.postToConnection(domain, stage, connectionId, data);await connectionStore.sendToUser(domain, stage, userId, data);await connectionStore.sendToTenant(domain, stage, tenantId, data);await connectionStore.broadcast(domain, stage, data);
// Queriesconst conn = await connectionStore.get(connectionId);const userConns = await connectionStore.getByUser(userId);const tenantConns = await connectionStore.getByTenant(tenantId);DynamoDB Table Schema
Section titled “DynamoDB Table Schema”Connection state is stored in DynamoDB:
| Field | Type | Description |
|---|---|---|
connectionId | String (PK) | API Gateway connection ID |
authenticated | Boolean | Whether authenticated |
userId | String | User ID from JWT |
tenantId | String | Tenant ID (optional) |
email | String | User email |
connectedAt | Number | Unix timestamp (ms) |
ttl | Number | DynamoDB TTL (epoch seconds) |
Required GSIs:
| GSI | Partition Key | Purpose |
|---|---|---|
userId-index | userId | getByUser(), sendToUser() |
tenantId-index | tenantId | getByTenant(), sendToTenant() |
Message Format
Section titled “Message Format”{ "action": "auth", "token": "<jwt>" }{ "action": "auth", "token": "<jwt>", "tenantId": "acme" }{ "action": "ping" }{ "action": "broadcast", "data": { "text": "Hello!" } }{ "action": "send", "to": "<userId>", "data": { "text": "Hello!" } }