WebSocket
WebSocket Event Monitoring and Filtering
Subscribe to real-time WebSocket events from Rhombus — filter and process policy alerts, device state changes, motion detection, and access control events.
On this page
- Event topic
- Event payload structure
- Core fields
- Change types
- Entity types
- Inspecting policy alerts
- Filtering events
- By entity type
- By change type
- By device
- Enriching events with REST API data
- Get camera name from device UUID
- Get alert details
- Output formats
- Formatted alert display
- Raw JSON output
- Integration patterns
- Webhook relay
- Slack notifications
- Database logging
The Rhombus WebSocket event stream delivers real-time notifications about activity in your organization that your credential's role can see. This guide covers the event structure, available entity types, and patterns for filtering and processing events.
Event topic
All organizational events are published to a single topic:
/topic/change/{orgUuid}Changes to many kinds of objects in your organization emit an event on this topic, from new policy alerts to camera state and door components; the event reference lists them. A connection receives the events its credential's role covers, policy alerts included; events for other devices and locations are dropped without an error (see What a connection receives).
Event payload structure
Each MESSAGE frame carries a JSON body. A new policy alert looks like this (shortened):
{
"entity": "POLICY_ALERT",
"type": "CREATE",
"update": {
"uuid": "Xq3vT1bKQ2m8c9Zr0aLwYg",
"type": "POLICY_ALERT_V2",
"deviceUuid": "ItkwWv1qT7Wy8-pY8KDesw",
"locationUuid": "Rk2pQ7yLT0qX4b1nVt8sJw",
"timestampMs": 1791367200000,
"policyAlertTriggers": ["MOTION_HUMAN"],
"textDescription": "Human detected at Front Lobby"
}
}Core fields
| Field | Type | Description |
|---|---|---|
entity | string | The kind of object that changed |
type | string | CREATE, UPDATE, DELETE or REFRESH |
entityUuid | string | The object that changed. Absent on a new policy alert: use update.uuid |
update | object | The object's new state or changed fields; absent on many messages. Its shape depends on entity |
deviceUuid | string | The device involved, on the few messages that set it |
subLocationsHierarchyKey | string | The sub-location path, on some alarm-monitoring messages |
The organization isn't in the body: it's in the destination and the ORG_UUID frame header. The event reference documents every field, including the full policy alert.
Change types
| Type | Meaning | Example |
|---|---|---|
CREATE | Something new, sent with the whole object | A new policy alert |
UPDATE | An existing object changed | A camera's state changed |
REFRESH | The object changed; read it again if you need its state | A door sensor reported a change |
DELETE | The object was removed, with no update | An alert was removed or archived |
Entity types
The entity field says what kind of object changed. The ones most integrations use:
| Entity | Sent when |
|---|---|
POLICY_ALERT | An alert is created (CREATE, with the alert), linked to a threat case (UPDATE) or removed (DELETE) |
CAMERA_STATE, AUDIO_STATE | A camera's or audio gateway's state changes (UPDATE, with the new state) |
DOOR_STATE, CLIMATE_STATE, OCCUPANCY_STATE, PROXIMITY_STATE | A sensor reports a change (REFRESH, with the changed fields) |
CAMERA, DOOR_SENSOR, CLIMATE_SENSOR, PROXIMITY_SENSOR | A device is added (REFRESH) |
ACCESS_CONTROLLED_DOOR, COMPONENT_*_SHADOW | An access-controlled door or one of its components changes |
POLICY, SCHEDULE | A policy or schedule is changed |
SAVED_CLIPS | A saved clip changes (REFRESH) |
The event reference lists every entity type. Every type arrives on the same destination; there's no server-side filter, so filter on entity in your handler.
Inspecting policy alerts
A new alert arrives as CREATE with the whole alert in update: its uuid, the camera (deviceUuid), timestampMs, the policyAlertTriggers that fired, and more. It's sent once, when the alert is first stored, so fields Rhombus fills in afterward (such as clip locations and finalized) may still be at their initial values; fetch the alert from the REST API when you need its final state.
def inspect_alert(payload):
if payload.get("entity") != "POLICY_ALERT" or payload.get("type") != "CREATE":
return
alert = payload["update"]
print(alert["uuid"], alert.get("deviceUuid"), alert["timestampMs"], alert.get("policyAlertTriggers"))Filtering events
By entity type
def handle_message(payload):
entity = payload.get("entity")
if entity == "POLICY_ALERT":
handle_alert(payload)
elif entity == "CAMERA_STATE":
handle_camera_state(payload)
else:
pass # ignore types you don't useBy change type
def handle_alert(payload):
change_type = payload.get("type")
if change_type == "CREATE":
send_notification(payload["update"]) # a new alert
elif change_type == "UPDATE":
refresh_alert(payload["entityUuid"]) # linked to a threat case
elif change_type == "DELETE":
remove_alert(payload["entityUuid"]) # removed or archivedDon't rely on DELETE messages to track every removed alert: they're limited to one per organization every 10 minutes.
By device
Where the device is depends on the entity: a policy alert names it in update.deviceUuid, and a device-state message is about the device in entityUuid.
WATCHED_DEVICES = {"ItkwWv1qT7Wy8-pY8KDesw"}
def device_of(payload):
if payload.get("entity") == "POLICY_ALERT":
return (payload.get("update") or {}).get("deviceUuid")
return payload.get("deviceUuid") or payload.get("entityUuid")
def handle_message(payload):
if device_of(payload) in WATCHED_DEVICES:
process_event(payload)Enriching events with REST API data
WebSocket events contain minimal data for efficiency. Use the REST API to fetch additional details when needed:
Get camera name from device UUID
import requests
def get_camera_name(api_token, device_uuid):
"""Fetch camera name for display purposes."""
response = requests.post(
"https://api2.rhombussystems.com/api/camera/getMinimalCameraStateList",
headers={
"x-auth-scheme": "api-token",
"x-auth-apikey": api_token,
"Content-Type": "application/json"
},
json={}
)
cameras = response.json().get("cameraStates", [])
for camera in cameras:
if camera.get("uuid") == device_uuid:
return camera.get("name", "Unknown Camera")
return "Unknown Camera"Get alert details
def get_alert_details(api_token, alert_uuid):
"""Fetch full policy-alert details from the REST API."""
response = requests.post(
"https://api2.rhombussystems.com/api/event/getPolicyAlertDetails",
headers={
"x-auth-scheme": "api-token",
"x-auth-apikey": api_token,
"Content-Type": "application/json"
},
json={"policyAlertUuid": alert_uuid}
)
return response.json().get("policyAlert")Cache camera names locally to avoid excessive REST API calls. Camera names change infrequently, so a cache with a 5-minute TTL works well.
Output formats
Formatted alert display
def display_alert(payload, camera_name=""):
entity = payload.get("entity", "UNKNOWN")
change = payload.get("type", "UNKNOWN")
update = payload.get("update", {})
print(f"{change} {entity} camera={camera_name}")
print(f" device={payload.get('deviceUuid', 'N/A')}")
print(f" uuid={payload.get('entityUuid', 'N/A')}")
# `update` is entity-specific; print its keys to discover available detail.
if update:
print(f" update keys={list(update.keys())}")
print()Raw JSON output
For piping to other tools or logging systems:
import json
def output_json(payload):
print(json.dumps(payload, indent=2))Integration patterns
Webhook relay
Forward Rhombus events to your own webhook endpoint:
import requests
WEBHOOK_URL = "https://your-server.com/webhooks/rhombus"
def relay_to_webhook(payload):
try:
requests.post(WEBHOOK_URL, json=payload, timeout=5)
except requests.RequestException as e:
print(f"Webhook relay failed: {e}")Slack notifications
Send high-priority alerts to a Slack channel:
def send_slack_alert(payload, webhook_url):
entity = payload.get("entity", "UNKNOWN")
change = payload.get("type", "UNKNOWN")
message = {
"text": f":rotating_light: *Security Alert*\n"
f"{change} {entity}\n"
f"Entity: {payload.get('entityUuid', 'N/A')} "
f"Device: {payload.get('deviceUuid', 'N/A')}"
}
try:
requests.post(webhook_url, json=message, timeout=5)
except requests.RequestException as e:
print(f"Slack notification failed: {e}")Database logging
Persist events for historical analysis:
import sqlite3
import json
def log_event(db_path, payload):
conn = sqlite3.connect(db_path)
conn.execute("""
INSERT INTO events (entity, entity_uuid, change_type, device_uuid, payload)
VALUES (?, ?, ?, ?, ?)
""", (
payload.get("entity"),
payload.get("entityUuid"),
payload.get("type"),
payload.get("deviceUuid"),
json.dumps(payload)
))
conn.commit()
conn.close()