Back to Blog

WebSocket Architecture for Real-Time Teams

Two Data Paths, One Product

FlowEra uses two distinct real-time data paths, each optimized for its use case:

PowerSync sync stream — persistent, reliable, bidirectional sync of all workspace data. This is how task changes, comments, and knowledge base edits propagate between clients. It’s built for correctness: every change is persisted to PostgreSQL and guaranteed to reach all subscribed clients.

WebSocket server (/api/ws) — ephemeral, low-latency events for interaction signals. This is how typing indicators, video call signaling (WebRTC offer/answer/ICE), and presence updates flow. These events don’t need to persist — a typing indicator from 2 seconds ago is useless — they need to arrive fast.

Separating these concerns means each path can be optimized independently.

The WebSocket Connection

Clients establish a WebSocket connection at /api/ws authenticated via access_token cookie. The connection carries tenant context from the JWT, so the server knows which workspace the client belongs to and can route messages correctly.

Connection state is managed on the server side per-client. When a client connects, it registers its user ID and tenant ID. When it disconnects, any associated presence state is cleaned up.

Typing Indicators

When a user begins typing in a channel or entity chat, the client sends a typing_start event over WebSocket. The server broadcasts it to all connected clients in the same channel. When the user stops typing (debounced at 3 seconds of inactivity), the client sends typing_stop.

We don’t persist typing events. They’re fire-and-forget. If a typing indicator is dropped — which happens occasionally on unstable connections — the user sees a slightly delayed indicator. This is acceptable. The alternative (persisting typing state to the database) creates unnecessary write load for ephemeral data.

Video Call Signaling

WebRTC peer connections require a signaling channel to exchange offer, answer, and ICE candidate messages before media streams can be established. FlowEra uses the WebSocket connection for this signaling.

Call participants exchange signaling messages through the server — client A sends an offer to the server, the server routes it to client B, client B responds with an answer. Once the peer connection is established, media flows directly between clients via WebRTC (peer-to-peer when possible, via TURN relay when NAT traversal requires it).

Signaling through WebSocket is fast enough that call connection typically completes in under 2 seconds on good connections.

Presence

Presence — knowing which team members are currently online — is derived from WebSocket connection state. When a user connects, they’re considered online. When they disconnect, they’re offline after a short grace period (to handle brief reconnects).

Presence state is stored in memory on the server, not in the database. This means presence information is accurate only for users connected to the same server instance. For multi-instance deployments, we use Redis pub/sub to broadcast presence events across instances.

Scaling Considerations

The WebSocket server is stateless in terms of business data — all authoritative data lives in PostgreSQL via PowerSync. The in-memory state (connected clients, presence) is supplemented by Redis for multi-instance consistency.

Horizontal scaling of the WebSocket server requires routing clients in the same channel or call to servers that can communicate with each other — either via shared Redis pub/sub or consistent hashing. This is the standard challenge for WebSocket scale-out, and we handle it through the Redis layer.

Authentication

Every WebSocket connection authenticates using the same JWT mechanism as REST endpoints. The access token is sent as a cookie on the WebSocket upgrade request — browsers include cookies on WebSocket handshakes by default, so clients don’t need special handling.

If the token expires during a long connection, the server closes the connection and the client reconnects with a refreshed token. Reconnection is automatic and typically transparent to the user.

Read more about FlowEra’s technical architecture