Skip to main content
Every entity change is delivered in real time to connected clients. The mechanism differs for users (frontend) and services (background processors).

WebSocket protocol

All real-time communication happens over WebSocket using a JSON protocol with a type discriminator field.

Events (Server → Client)

TypeRecipientDescription
Entity ChangeUsersEntity changed matching your subscription
Entity Change (with task)ServicesEntity change requiring acknowledgment
Entity Change BatchServicesBatch of changes requiring acknowledgment
Subscription ResultBothSubscribe success/failure
Subscription RefreshUsersEntity left your subscription — refetch your list
HeartbeatBothConnection alive
Log BroadcastAdminsService log message

Commands (Client → Server)

CommandDescription
SubscribeSubscribe to entities matching a query
UnsubscribeStop receiving changes
Task CompletionAcknowledge processing complete (services only)
Lock / UnlockDistributed lock management (services only)
HeartbeatKeep-alive ping

User data flow

1

Entity committed

A change is persisted and a checkpoint is created.
2

Broadcast

The change is broadcast to all API nodes via pub/sub.
3

Subscription evaluation

Each node evaluates the change against every connected user’s subscriptions. If the entity matches the query, an entity change event is sent. If the entity was being watched but no longer matches, a refresh event is sent telling the client to refetch its list.
User changes are fire-and-forget. Users don’t acknowledge receipt. If a user is offline when a change happens, they’ll get the current state when they reconnect and subscribe.

Subscription refresh

When an entity leaves a user’s subscription (e.g., an account’s workstream changes from “DenialManagement” to “None” while a user is viewing the denial queue), a refresh event is sent. This tells the client: “an entity you were watching no longer matches your query — refetch your list.”

Service data flow

1

Entity committed

A change is persisted and a checkpoint is created.
2

Query matching

The change is evaluated against all background service queries.
3

Enqueue

Matching changes are enqueued to the service’s dedicated message queue.
4

Delivery

The service receives the change event via its WebSocket connection.
5

Acknowledge

The service processes the change and sends an acknowledgment (success or failure).
Key difference from users:
  • Durable — messages persist even if the service is offline
  • Acknowledged — services must confirm processing
  • Retried — unacknowledged messages are re-delivered after a timeout

Multi-node architecture

Our API runs across multiple nodes behind a load balancer. The challenge: a change committed on Node A must reach a user connected to Node B. Changes are broadcast to all nodes via a pub/sub bus. Each node evaluates the change against its locally connected users’ subscriptions and delivers matching events. This ensures every user gets real-time updates regardless of which node they’re connected to.

Change event payload

An entity change event includes:
{
  "change": {
    "entityId": "entity-uuid",
    "previous": { /* full entity before change, null if creation */ },
    "current": { /* full entity after change */ },
    "createdAt": "2026-02-19T14:30:00Z",
    "createdBy": "user-uuid"
  }
}
Both the previous and current state are included, so clients can compute what changed without an additional API call.