Overview
UniSave uses a nudge model for real-time updates: the WebSocket sends lightweight event notifications, and clients fetch the actual data via HTTP delta endpoints. This keeps the WebSocket protocol simple and the delta sync the single source of truth.Connecting
Authorization: Bearer <token> header or ?token= query parameter (fallback for environments that don’t support custom headers on WebSocket).
Connection Limits: Maximum 5 concurrent WebSocket connections per user. Exceeding this limit closes the newest connection with status 4008 Policy Violation.
Event Envelope
All events use this JSON envelope:| Field | Type | Description |
|---|---|---|
type | string | Event type (see table below) |
ts | integer | Unix timestamp (seconds) when the event was emitted |
payload | object | Optional event-specific data. Present for enrichment.done, omitted for change nudges. |
Event Types
| Type | Description | Payload | Client Action |
|---|---|---|---|
bookmark.changed | One or more bookmarks were created, updated, or deleted | — | Fetch /bookmarks/delta |
collection.changed | Collections were modified | — | Fetch /collections/delta |
enrichment.done | An enrichment job finished (success or failure) | { "clientBookmarkId": "abc-123", "status": "completed" } | Fetch /enrichment/status or /bookmarks/delta |
Architecture
The WebSocket infrastructure uses PostgreSQLNOTIFY/LISTEN:
- Database triggers fire
NOTIFYon bookmark/collection/enrichment changes - The Go backend listens for these notifications via a persistent connection
- The Hub routes notifications to the correct user’s WebSocket connections
- Clients receive lightweight nudges and fetch data via existing HTTP endpoints
- No data duplication — WebSocket carries events, HTTP carries data
- No missed updates — delta sync with cursors is the source of truth
- Scalable — multiple API instances can each listen to Postgres notifications
Timeouts & Keepalive
- Server sends ping frames every 30 seconds — connection closes if pong is not received within 10 seconds
- Token expiry is checked on each ping cycle — expired tokens cause a graceful close with status
1001 Going Away - Clients should implement automatic reconnection with exponential backoff