> ## Documentation index
> Fetch the complete documentation index at: https://developer.rhombus.com/llms.txt
> Use it to discover all available pages before exploring further.

# 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.

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:

```text
/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](https://developer.rhombus.com/websocket/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](https://developer.rhombus.com/websocket/authentication#what-a-connection-receives)).

## Event payload structure

Each `MESSAGE` frame carries a JSON body. A new policy alert looks like this (shortened):

```json
{
  "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](https://developer.rhombus.com/websocket/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](https://developer.rhombus.com/websocket/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.

```python
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

```python
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 use
```

### By change type

```python
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 archived
```

Don'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`.

```python
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

```python
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

```python
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")
```

> **Note**: 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

```python
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:

```python
import json

def output_json(payload):
    print(json.dumps(payload, indent=2))
```

## Integration patterns

### Webhook relay

Forward Rhombus events to your own webhook endpoint:

```python
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:

```python
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:

```python
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()
```
