Appearance
Websocket & Events – Application Guide
This chapter explains how frontend applications and external services use the Websocket API of the coldwave backend to receive live updates and events.
It focuses on:
- How to open a websocket connection (ticket-based and direct)
- How to send healthcheck pings and handle reconnects
- How events from different modules (Service, Streams, Meta, Alarms, Notes, IAM) are delivered and how to route them in your app
- How IAM permissions and device/service scoping apply to events
The low-level event schemas remain documented in the individual module pages (Service, Streams, Meta, Alarms, Notes, IAM, …). This guide explains how to put everything together in an application.
TIP
This guide describes the second iteration of the websocket module. Refer to the backend configuration if you need to run v1 and v2 in parallel.
1. When and why to use websockets
The REST API gives you the current state (services, properties, meta, …).
The websocket gives you changes over time, for example:
- A property value changes (
OBJECT_UPDATEDfrom the Service module) - A livestream emits new samples (
BIN_STREAM_UPDATE/STRING_STREAM_UPDATE) - A device goes online/offline/idle (
META_STATUS_CHANGE) - An alarm becomes active or inactive (
ALARM_STATUS_ACTIVE/ALARM_STATUS_INACTIVE) - A note is added to a device (
NOTE_MESSAGE_ADD) - An IAM object changes (API keys, users, roles, …)
Typical uses in a UI:
- Realtime device dashboards
- Live log/trace views
- “Online/Offline” badges
- Alarm center, notification counters
- Collaborative tools (notes, admin consoles)
The recommended pattern is:
- Load initial state via REST (e.g.
/api/v1/devices,/api/v1/meta,/api/v1/alarms/status) - Open a websocket
- Apply incoming events to your local state store
2. Connection options
The websocket module exposes three endpoints:
GET /api/v1/events/ticket– create a short-lived ticket for browsersGET /api/v1/events/:ticket– open a websocket using a ticketGET /api/v1/events– open a websocket with anAuthorizationheader
All endpoints authenticate the connection using the same IAM token that is used for REST calls. The IAM permissions and device/service scoping fully apply to which events you will see.
2.1 Browser / SPA: ticket-based connection
Most browser WebSocket implementations do not allow setting arbitrary HTTP headers (like Authorization) during the upgrade request. For that scenario, use the ticket flow:
Your SPA calls
GET /api/v1/events/ticketwith the regular Bearer token:httpGET /api/v1/events/ticket Authorization: Bearer <<TOKEN>>Response (simplified):
json{ "ticket": "abc123..." }The ticket is valid for about 30 seconds.
The SPA opens a websocket using the ticket in the URL:
tsconst ws = new WebSocket("wss://<<URL>>/api/v1/events/" + ticket);If the ticket is valid and matches the token used to create it, the backend upgrades the connection and starts streaming events.
Recommendation
Keep the ticket request in the same place where you also obtain/refresh tokens. If your app uses refresh tokens, always create a new ticket after getting a new access token.
2.2 Backend / CLI: direct connection with header
For non-browser clients (Node.js services, Go/Java backends, CLI tools) it is often easier to send the Authorization header directly:
http
GET /api/v1/events
Authorization: Bearer <<TOKEN>>How you attach headers depends on the websocket client library you use. The response status is 204 on success; the actual data comes in as websocket messages.
Use this variant if:
- you fully control the HTTP layer (no browser restrictions), and
- you already manage tokens in that process.
3. Message format and routing
Each websocket message is a JSON object describing a single event. The exact shape depends on the module that produced it, but all events have a type field, for example:
OBJECT_UPDATED(Service)META_STATUS_CHANGE(Metadata)CREATE_LIVESTREAM,BIN_STREAM_UPDATE(Streams)ALARM_STATUS_ACTIVE(Alarms)NOTE_MESSAGE_ADD(Notes)IAM_KEY_ADD(IAM)
A simple event router in TypeScript might look like this:
ts
type ColdwaveEvent =
| ServiceObjectUpdatedEvent
| MetaStatusChangeEvent
| StreamEvent
| AlarmEvent
| NoteEvent
| IamEvent
| HealthcheckEvent
| { type: string; [key: string]: unknown }; // fallback
function handleEvent(evt: ColdwaveEvent) {
switch (evt.type) {
case "OBJECT_UPDATED":
handleServiceUpdate(evt);
break;
case "META_STATUS_CHANGE":
case "META_SET":
case "META_DELETE":
handleMetaEvent(evt);
break;
case "CREATE_LIVESTREAM":
case "UPDATE_LIVESTREAM":
case "DELETE_LIVESTREAM":
case "BIN_STREAM_UPDATE":
case "STRING_STREAM_UPDATE":
handleStreamEvent(evt);
break;
case "ALARM_STATUS_ACTIVE":
case "ALARM_STATUS_INACTIVE":
case "ALARM_CONFIG_ADD":
case "ALARM_CONFIG_UPDATE":
case "ALARM_CONFIG_DELETE":
handleAlarmEvent(evt);
break;
case "NOTE_MESSAGE_ADD":
case "NOTE_MESSAGE_UPDATE":
case "NOTE_MESSAGE_DELETE":
handleNoteEvent(evt);
break;
case "IAM_KEY_ADD":
case "IAM_KEY_UPDATE":
case "IAM_KEY_DELETE":
case "IAM_USER_CREATE":
case "IAM_USER_UPDATE":
handleIamEvent(evt);
break;
case "HEALTHCHECK_PONG":
handleHealthcheck(evt);
break;
default:
// Optional: log or ignore unknown event types
break;
}
}In a real app you would:
- Keep a single websocket connection for the whole app.
- Parse messages in one central place (
ws.onmessage). - Dispatch to module-specific slices in your state store (Redux, Vuex, Pinia…).
4. Healthcheck and connection lifecycle
4.1 Healthcheck ping/pong
The websocket module exposes a simple healthcheck mechanism:
Client sends a JSON message:
tsws.send(JSON.stringify({ type: "HEALTHCHECK_PING" }));Backend responds with:
json{ "type": "HEALTHCHECK_PONG" }
You can use this to:
- Confirm that the connection is still alive.
- Measure latency (timestamp in payload is optional and can be added on the client side).
It is usually not needed for every app – the websocket close/error events already tell you if the connection broke – but it is useful for diagnostics and debugging.
4.2 Reconnect strategy
A robust reconnect strategy for SPAs/backends usually has these elements:
On websocket
closeorerror:- Wait for a small delay (e.g. 1–5 seconds, with exponential backoff).
- Ensure the current access token is still valid (refresh if needed).
- For browsers, request a new ticket with the fresh token.
- Open a new websocket connection.
After reconnect:
- Optionally re-fetch critical state via REST (alarms, notes, active streams).
- Continue handling events as before.
Avoid opening multiple websocket connections per tab – one global connection per app instance is usually enough.
5. Module-specific events and how to use them
This section summarizes the most important event types for application developers. The full and authoritative schemas are in the individual module docs; here we focus on how to use them in your state model and UI.
5.1 Service: OBJECT_UPDATED
From the Service module you receive OBJECT_UPDATED events whenever a service updates a property:
type: "OBJECT_UPDATED"deviceIdentifierserviceIdentifier- optional
identifier(for multiple instances of the same service) updates: array of property updates
You typically use this to:
- Update the in-memory representation of a device/service in your state store.
- Re-render dashboards, detail views, trends that depend on the latest values.
The updates array uses the same property representation (ID, type, modifiers) as the Service REST API. If you keep your state in the “schema-mapped” format, you can use the property identifier or schema name to apply the change.
Pattern:
- On
OBJECT_UPDATED:- Locate your cached device/service object.
- Apply the property changes (value and any modifier flags).
- If the service is not in cache yet, fetch it once via REST with
depth.
5.2 Metadata: META_* events
From the Meta module you receive, among others:
META_STATUS_CHANGE– device online/offline/idle changesMETA_SET/META_DELETE– user-defined metadata changesMETA_SCHEMA_ADD/META_SCHEMA_UPDATE/META_SCHEMA_DELETE– meta schema changes
Typical uses:
- Update a device’s
statusandlastMessage/lastConnectfields for “online indicator” badges. - Keep a local cache of metadata in sync when metadata is edited in another tab or by another user.
- Live-refresh meta schema editors (admin UIs) when schemas are created or updated.
5.3 Streams: LIVESTREAM_* and *_STREAM_UPDATE
From the Stream module you receive:
CREATE_LIVESTREAM,UPDATE_LIVESTREAM,DELETE_LIVESTREAMBIN_STREAM_UPDATE,STRING_STREAM_UPDATE
These events are tied to open property streams created via the /api/v1/streams API. Only the user who created a stream will receive the corresponding events.
Typical uses:
- Realtime charts for fast-changing properties (e.g. speed, power consumption).
- Log/trace views for string or binary streams.
Pattern:
- App calls
POST /api/v1/streamsfor a given device/service/property. - Backend acknowledges with a
uuidand sends aCREATE_LIVESTREAMevent. - As data arrives,
BIN_STREAM_UPDATE/STRING_STREAM_UPDATEevents contain the samples. - The app appends samples to an in-memory buffer or chart.
- When the view is closed, the app calls
DELETE /api/v1/streams/:uuidand removes its local buffers.
5.4 Alarms: ALARM_CONFIG_* and ALARM_STATUS_*
From the Alarm module you receive events like:
ALARM_CONFIG_ADD,ALARM_CONFIG_UPDATE,ALARM_CONFIG_DELETEALARM_STATUS_ACTIVE,ALARM_STATUS_INACTIVE,ALARM_STATUS_DELETE_DEVICE
Typical uses:
- Keep the list of alarm configurations in sync between multiple open UIs.
- Update the “current alarm status” for each device without polling.
- Show toast/notifications when an alarm becomes active.
Pattern:
- Use REST to initially load alarm configs and status.
- Subscribe to the events to update your alarm store incrementally.
- For user-facing notifications, subscribe only to severe alarms (filter by severity on the backend or in your handler).
5.5 Notes: NOTE_MESSAGE_*
From the Notes module you receive:
NOTE_MESSAGE_ADDNOTE_MESSAGE_UPDATENOTE_MESSAGE_DELETE
Typical uses:
- Live collaboration on per-device notes.
- Show “new note” indicators on device tiles or timeline views.
Pattern:
- REST: load all notes for a device when its detail page opens.
- Websocket: apply changes as events arrive and update the list in-place.
5.6 IAM: IAM_* events
From the IAM module you receive various IAM_* events, for example:
IAM_KEY_ADD,IAM_KEY_UPDATE,IAM_KEY_DELETEIAM_GROUP_ADD,IAM_GROUP_UPDATE,IAM_GROUP_DELETEIAM_ROLE_ADD,IAM_ROLE_UPDATE,IAM_ROLE_DELETEIAM_USER_CREATE,IAM_USER_UPDATE,IAM_USER_DELETE, …
These are mainly relevant for admin / operator UIs:
- Live update of user/role/group lists when multiple admins are active.
- Refresh of API key lists when keys are created/rotated/deleted.
For a typical end-user portal or dashboard, you may not need to act on IAM events at all.
6. Permissions and scoping for events
Websocket events use the same IAM model as REST calls:
- You only receive events for which your token has appropriate permissions:
- e.g.
readonserviceforOBJECT_UPDATED readonmetaforMETA_STATUS_CHANGEreadonstreamfor livestream eventsreadonalarms.status/alarms.configfor alarm eventsreadonnotesfor notes eventsreadoniam.*for IAM events
- e.g.
- Device and service scoping is applied:
- If your token is restricted to certain
deviceIdentifierorserviceIdentifiervalues, you will only see events for those devices/services. - Using wildcards (
*) in IAM grants broader access and therefore more events.
- If your token is restricted to certain
For application developers this means:
- If you “miss” expected events, check IAM roles and device/service scoping first, before debugging your websocket code.
- You do not need to filter events by device/service yourself to enforce access control – this is already done in the backend.
7. Implementation patterns and pitfalls
7.1 One websocket per app
- Open a single websocket connection per browser tab/app instance.
- Use it as a “bus” and route messages to feature modules (services, meta, streams, alarms, notes, IAM).
- Avoid opening a new connection for each component or feature – this wastes resources on both client and server.
7.2 Initial snapshot + incremental updates
- On app startup or when entering a screen:
- Use REST to fetch the initial state (devices, services, alarms, notes, …).
- Use websocket events to:
- update values,
- add/remove entities,
- and keep derived UI elements in sync.
Do not try to reconstruct full state purely from websocket events; REST remains the primary source of truth.
7.3 Token expiry and reconnect
- Websocket connections do not automatically renew IAM tokens.
- When your access token expires:
- REST calls will start to fail with 401/403,
- the websocket might also be closed by the backend.
- Handle this by:
- Watching token expiry server-side or via a timer,
- Refreshing the token using the IAM refresh endpoint,
- Closing and reopening the websocket with the new token/ticket.
7.4 Error handling and logging
- Treat unknown
typevalues in events as non-fatal – log them for debugging, but do not break the connection. - In development, log all events (or subsets) to the console to debug your routing logic.
- In production, log only unexpected event types and connection errors.
With these patterns in place, the websocket module becomes the backbone of your live-updating UI, while the REST modules (Service, Meta, Streams, Timestream, Alarms, Notes, IAM) provide the initial data and on-demand queries.