Real-time API (WebSocket)
The Noccela real-time API streams positions, tag states, alerts and layout changes over WebSocket: connecting, authentication, message format, message types.
The real-time API is a WebSocket connection to the server node that runs a site’s positioning engine. It is the right choice when you need positions and state changes as they happen. For data you need once in a while, such as hourly reports, the HTTP API is simpler.
Connecting
- Look up the domain. Call
GET /realtime/domainon the HTTP API with the account and site IDs. The response names the node, for exampleapi.n1.fi1.noccela.io. The node for a site can change, so look it up before every connection. Connecting to the wrong node, or to a site that is offline, shows up as a timeout with no data. - Open the socket at
wss://{domain}/realtime?account={accountId}&site={siteId}. - Authenticate. Send the access token as the first message, as plain text with no JSON around it. If it is rejected, the server closes the socket. If it is accepted, you receive:
{
"uniqueId": "authSuccess",
"status": "ok",
"payload": {
"tokenExpiration": 1792742400,
"tokenIssued": 1792656000,
"cloudVersion": "v3.x"
}
}
tokenExpiration and tokenIssued are Unix timestamps in seconds (example values above). See authentication for getting the token.
Message format
There are three kinds of messages.
Pings. The server sends an empty message. Reply with the string 1. If pings are not answered, the connection times out after two minutes.
Requests and responses. You send a request; the server answers with the same uniqueId. All three properties are required; send an empty object when there is no payload.
{ "uniqueId": "42", "action": "registerTagLocation", "payload": { "deviceIds": null } }
{ "uniqueId": "42", "status": "ok", "payload": {} }
status is ok on success, or an error such as badRequest, forbidden or serverError. Every request gets a response, so you can confirm that a subscription was registered.
Server events. Subscriptions produce messages without a uniqueId, named by action:
{ "action": "locationUpdate", "status": "ok", "payload": { "…": "…" } }
MessagePack payloads
To save bandwidth, initialTagState and initialAlertState arrive as a Base64 string containing MessagePack. Decode the Base64 to bytes, decode the bytes with any MessagePack library, and map the array positions to field names (tag state: see the quickstart). Base station state can be compact or JSON, chosen with useCompactData.
Message types
Requests marked subscription register a stream of server events; each has a matching unregister… action that takes the same filters you registered with. Most subscriptions accept deviceIds: an array of device IDs, or null for every device on the site.
| Action | Kind | Server event | Purpose |
|---|---|---|---|
getSite |
Request | Site details, including the full layout | |
initialTagState |
Request | initialTagState |
Full state of all or selected tags, once (MessagePack) |
registerTagDiffStream |
Subscription | tagDiffStream |
Changed tag properties, plus removedTags |
registerTagLocation |
Subscription | locationUpdate |
Coordinates only: the lightest way to follow positions |
initialAlertState |
Request | initialAlertState |
Alerts within given date ranges, once (MessagePack) |
registerAlertDiffStream |
Subscription | alertDiffStream |
New and updated alerts, plus removedAlerts |
alert |
Request | The path of a tag leading up to an alert | |
initialContactTracingState |
Request | Proximity (contact) events within a time span | |
registerContactTracingStream |
Subscription | contactTracingUpdate |
Proximity events as they happen |
registerP2PDistanceStream |
Subscription | p2pDistanceUpdate |
Tag-to-tag distances, where peer-to-peer ranging is enabled |
registerTwrStream |
Subscription | twrStreamData |
Raw tag-to-base-station distances; disabled by default, agree with Noccela first |
getBlueprint |
Request | A floor-plan image as Base64 | |
getImage |
Request | Another image, such as a tag or tag-group picture | |
sendSignal |
Request | Set the modules of a signalling device such as the Signalling Flash | |
playTagBuzzer |
Request | Make tags beep and flash their LED | |
registerLayoutChanges |
Subscription | siteLayoutChanged |
Notice when the site’s layout changes |
refreshToken |
Request | Replace the access token on an open connection | |
getInitialBeaconState |
Request | initialBeaconState |
Online, charging and voltage state of base stations |
registerToBeaconChangeStream |
Subscription | beaconDiffStream |
Changes in base station state |
getAvailableBeacons |
Request | Base stations assigned to the account but not to another site | |
saveLayout |
Request | Save a modified layout (replaces the whole layout) | |
fillPolygon |
Request | Fill a polygon with smaller, non-overlapping polygons | |
calibratePositions |
Request | calibratePositionsResponse |
Calibrate base station positions from their ranging data |
Key messages in detail
Location updates
The lightest way to follow positions. Each event can contain several tags, keyed by device ID.
{
"action": "locationUpdate",
"payload": {
"1234567": {
"x": 15320,
"y": 8410,
"z": 1200,
"floor": 2,
"timestamp": 1792656012345,
"twrTimestamp": 1792656012301,
"uncertaintyDistance": 90
}
}
}
Example values. floor is omitted on single-floor sites. timestamp is when the engine computed the position and twrTimestamp when the distances were measured, both in milliseconds since the Unix epoch. If uncertaintyDistance is present, the real position lies within that radius.
Tag state and changes
Request initialTagState once, then keep it current with registerTagDiffStream. A tagDiffStream event contains only the properties that changed, except isOnline, which is always sent:
{
"action": "tagDiffStream",
"status": "ok",
"payload": {
"tags": { "1234567": { "batteryStatus": 2, "isOnline": true } },
"removedTags": [7654321]
}
}
The fields are listed in the data model. If the connection breaks, some changes may be lost: request initialTagState again after reconnecting.
Alerts
initialAlertState takes a list of date ranges as ISO 8601 strings (prefer one range per request) and returns all alerts in them. registerAlertDiffStream then sends new alerts and changes, keyed by alert ID as a string. alert with an alertId returns the points (x, y, timestamp, floorId) that led to the alert.
Proximity events and distances
contactTracingUpdate events report when two tags were closer than a configured distance: tag1, tag2, the closest distance in centimetres, duration in seconds, start and stop, a level (Major or Minor, two configurable radii) and a source: RTLS when computed from positions, P2P when the tags measured each other directly. For peer-to-peer events, beacon is the base station the tag uploaded the data through.
p2pDistanceUpdate reports individual tag-to-tag measurements in millimetres, on sites where peer-to-peer ranging is enabled. twrStreamData reports raw distances between tags (tId) and base stations (bId) in millimetres (d); it is disabled by default.
Making tags signal
playTagBuzzer makes tags beep and blink:
{
"uniqueId": "43",
"action": "playTagBuzzer",
"payload": {
"devices": [1234567],
"alertSound": false,
"buzzerSeconds": 5,
"ledSeconds": 5,
"ledColor": "red",
"ledBlinkFrequency": 2,
"playWithDelay": true
}
}
devices: null addresses every tag on the site, so pass a list unless that is what you want. alertSound selects the loud alert instead of the quiet buzzer. Defaults are 15 s for buzzer and LED, green LED and 2 Hz blinking. A sleeping tag cannot receive the command; with playWithDelay: true it is delivered when the tag wakes up, otherwise it is dropped. The HTTP API offers the same as POST /device/buzzer.
sendSignal drives a signalling device: deviceId and a list of modules, each with an index (0 or 1) and a value.
Base station state
getInitialBeaconState returns, per base station, whether it is online and, where the hardware reports them, its charging state and battery voltage (mobile base stations). Null values mean the hardware does not report that value. registerToBeaconChangeStream sends only changed values.
Layout and calibration
registerLayoutChanges sends siteLayoutChanged with the new layout’s minorId when someone edits the site; fetch the layout again with getSite. saveLayout saves a modified layout and replaces the previous one completely, so start from the current layout. calibratePositions runs automatic calibration: send the base stations with their positions and a status (0 unknown and ignored, 1 approximate, 2 calibrated, 3 measured and used as a reference) plus optional tag points. The result arrives later as a calibratePositionsResponse event rather than as the response to your uniqueId, so start listening for it before you send the request.
Reconnecting
Connections break: networks drop, nodes restart and sites move between nodes. A robust client:
- Reconnects with a back-off delay, looking up the domain again first.
- Authenticates with a valid token.
- Registers its subscriptions again.
- Requests
initialTagStateagain, because changes made while it was offline are not replayed.
The JavaScript SDK does all four for you.
Last updated 22 October 2026