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

const ws = new WebSocket("wss://api.unisave.io/v1/ws?token=JWT_ACCESS_TOKEN");

ws.onmessage = (event) => {
  const msg = JSON.parse(event.data);
  if (msg.type === "bookmark.changed") {
    // Trigger delta sync
    syncBookmarks();
  }
};
Authentication: JWT access token passed via 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:
{
  "type": "bookmark.changed",
  "ts": 1712919600,
  "payload": {}
}
FieldTypeDescription
typestringEvent type (see table below)
tsintegerUnix timestamp (seconds) when the event was emitted
payloadobjectOptional event-specific data. Present for enrichment.done, omitted for change nudges.

Event Types

TypeDescriptionPayloadClient Action
bookmark.changedOne or more bookmarks were created, updated, or deletedFetch /bookmarks/delta
collection.changedCollections were modifiedFetch /collections/delta
enrichment.doneAn enrichment job finished (success or failure){ "clientBookmarkId": "abc-123", "status": "completed" }Fetch /enrichment/status or /bookmarks/delta

Architecture

The WebSocket infrastructure uses PostgreSQL NOTIFY/LISTEN:
  1. Database triggers fire NOTIFY on bookmark/collection/enrichment changes
  2. The Go backend listens for these notifications via a persistent connection
  3. The Hub routes notifications to the correct user’s WebSocket connections
  4. Clients receive lightweight nudges and fetch data via existing HTTP endpoints
This design means:
  • 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