Skip to content

Reading Events with SSE

RT Server publishes a live stream of everything a running instance produces — object tracks, analytics events (intrusions, line crossings, …), object crops, attributes, and periodic statistics — over Server-Sent Events (SSE). SSE is a standard, one-way HTTP streaming protocol supported natively by browsers (EventSource) and by simple line-based parsers in any language.

This is the same stream the Web Panel Live View uses. If you only need events in an external system such as a broker or database, see Configuring Exporting (MQTT) and the Database queries below; SSE is the right choice when you want a live push stream without any extra infrastructure.

The endpoint

GET /v1/core/instance/{instanceId}/consume_events_sse

The instance must be running for events to flow. The connection stays open and the server pushes events as they happen, with these response headers:

HTTP/1.1 200 OK
Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive
Transfer-Encoding: chunked

Message format

Each event is delivered as a standard SSE record with three fields:

id: 286
event: track
data: {"$id":"track","$version":1,"object_class":"Person", ... }
  • id - a monotonically increasing sequence number for the connection.
  • event - the event type (the SSE "event name"). Use this to route handling.
  • data - the event payload as a single-line JSON string. Every payload also repeats its type in the $id field and its schema version in $version.

Event types

event name Description
track An update for a tracked object (position, class, confidence). High frequency.
crop A cropped image of a detected object, linked to a track/event.
attribute An attribute inferred for a track (for example a vehicle color or person attribute).
statistics Periodic processing statistics for the instance.
event-intrusion / event-intrusion-end An object entered / left an intrusion area.
event-line-crossing An object crossed a tripwire.
event-area-enter / event-area-exit Area crossing events.
event-loitering / event-loitering-end Loitering started / ended.
event-crowd-detection A crowding threshold was reached.
event-dwelling A dwelling event.
event-tailgating A tailgating event on a line.
event-object-left-removed An object was left behind or removed.
event-armed-person / event-armed-person-end Armed-person detection started / ended.
event-fallen-person / event-fallen-person-end Fallen-person detection started / ended.
event-count-changed A counting line total changed.
event-status-changed An analytics status change.

The exact JSON schema for every event type is published at https://bin.cvedia.com/schema/.

Filtering the stream

By default the stream delivers every event type, and track events alone can be high volume. Restrict the stream to the types you care about with the events query parameter (comma-separated):

GET /v1/core/instance/{instanceId}/consume_events_sse?events=event-intrusion,event-line-crossing

Only the listed event types are sent on that connection.

Consuming the stream

JavaScript (browser)

The browser's native EventSource handles reconnection automatically. Register a listener per event type:

const instanceId = "899c89f9-90c3-c633-8586-8f09b005ebed";
const url = `http://localhost:3546/v1/core/instance/${instanceId}/consume_events_sse`;

const source = new EventSource(url);

source.addEventListener("event-intrusion", (e) => {
  const ev = JSON.parse(e.data);
  console.log(`Intrusion in "${ev.area_name}" by ${ev.object_class}`);
});

source.addEventListener("event-line-crossing", (e) => {
  const ev = JSON.parse(e.data);
  console.log(`${ev.object_class} crossed "${ev.tripwire_name}" going ${ev.crossing_direction}`);
});

source.addEventListener("track", (e) => {
  const t = JSON.parse(e.data);
  // high frequency — update an overlay, etc.
});

source.onerror = () => console.warn("SSE connection lost; the browser will retry");

Python

No extra dependency is required — SSE records are blank-line-delimited blocks of field: value lines:

import json
import requests

instance_id = "899c89f9-90c3-c633-8586-8f09b005ebed"
url = f"http://localhost:3546/v1/core/instance/{instance_id}/consume_events_sse"
params = {"events": "event-intrusion,event-line-crossing"}

with requests.get(url, params=params, stream=True) as r:
    event_type, data = None, None
    for line in r.iter_lines(decode_unicode=True):
        if line == "":                      # blank line ends a record
            if event_type and data:
                payload = json.loads(data)
                print(event_type, "->", payload.get("object_class"))
            event_type, data = None, None
        elif line.startswith("event:"):
            event_type = line[len("event:"):].strip()
        elif line.startswith("data:"):
            data = line[len("data:"):].strip()

curl

For a quick look at the raw stream, use curl -N (no buffering):

curl -N "http://localhost:3546/v1/core/instance/<instanceId>/consume_events_sse?events=event-intrusion"

Example payloads

The payloads below were captured live from a running SecuRT instance.

event-intrusion

{
  "$id": "event-intrusion",
  "$version": 1,
  "event_id": "2ae0877d-b082-437b-afb1-6dc3d27e0aaa",
  "area_id": "6cbd8545-0889-4e27-8d7b-e313c9643d2a",
  "area_name": "Front gate",
  "instance_id": "899c89f9-90c3-c633-8586-8f09b005ebed",
  "object_class": "Person",
  "ref_tracking_id": "f57cf500-6aa5-4cc9-881e-2a9e819b9ff4",
  "location": { "x": 0.205, "y": 0.821, "width": 0.033, "height": 0.120 },
  "event_timestamp_ms": 32081,
  "system_timestamp": 1781889224024,
  "system_datetime": "2026-06-19T17:13:44Z"
}

The matching event-intrusion-end carries the same event_id plus an event_duration_ms field, letting you measure how long the object stayed inside the area.

event-line-crossing

{
  "$id": "event-line-crossing",
  "$version": 1,
  "event_id": "b67033ef-0d89-467f-b9d8-da4b4d8fd91e",
  "tripwire_id": "d52795da-61c8-4761-8303-d9c625895c0b",
  "tripwire_name": "Doorway",
  "instance_id": "899c89f9-90c3-c633-8586-8f09b005ebed",
  "object_class": "Person",
  "ref_tracking_id": "9c62ff0e-58a9-4bfa-8ebf-e44fe18b5fd2",
  "crossing_direction": "down",
  "location": { "x": 0.776, "y": 0.472, "width": 0.022, "height": 0.067 },
  "event_timestamp_ms": 33281,
  "system_timestamp": 1781889225226,
  "system_datetime": "2026-06-19T17:13:45Z"
}

track

{
  "$id": "track",
  "$version": 1,
  "tracking_id": "ccfd3025-49a2-4421-af19-563502811c9d",
  "instance_id": "899c89f9-90c3-c633-8586-8f09b005ebed",
  "object_class": "Person",
  "is_moving": 1,
  "detection_confidence": 0.872,
  "age_ms": 9600,
  "location": { "x": 0.300, "y": 0.206, "width": 0.013, "height": 0.053 },
  "events": ["abe32aeb-970c-4cf3-a097-fa33dbbe64d6"],
  "event_timestamp_ms": 29681,
  "system_timestamp": 1781889221623,
  "system_datetime": "2026-06-19T17:13:41Z"
}

The events array links a track to any analytics events it triggered.

crop

{
  "$id": "crop",
  "$version": 1,
  "ref_event_id": "2ae0877d-b082-437b-afb1-6dc3d27e0aaa",
  "ref_tracking_id": "f57cf500-6aa5-4cc9-881e-2a9e819b9ff4",
  "confidence": 0.932,
  "image": "/9j/4AAQSkZJRgABAQAAAQABAAD...",
  "location": { "x": 0.223, "y": 0.846, "width": 0.086, "height": 0.154 },
  "crop_timestamp_ms": 31481,
  "event_timestamp_ms": 32081,
  "instance_id": "899c89f9-90c3-c633-8586-8f09b005ebed",
  "system_timestamp": 1781889224024,
  "system_datetime": "2026-06-19T17:13:44Z"
}

image is a base64-encoded JPEG. Use ref_event_id / ref_tracking_id to associate the crop with the event or track that produced it.

Coordinates are normalized

Every location bounding box is normalized to the frame: x / y are the top-left corner and width / height are the size, all in the range 0.01.0. Multiply by the frame's pixel dimensions to get pixel coordinates.

Polling alternative

If a persistent connection is impractical, the same events can be drained with a regular request:

GET /v1/core/instance/{instanceId}/consume_events

It returns the events accumulated since the last call as an array of { dataType, jsonObject } pairs, or 204 No Content when no events are pending:

[
  { "dataType": "event-intrusion", "jsonObject": "{\"$id\":\"event-intrusion\", ...}" }
]

jsonObject is a JSON-encoded string with the same payload described above. Poll this endpoint on an interval; each call consumes (and clears) the pending events.

Querying historical events

SSE and polling deliver live events. To query past events and aggregated analytics from the on-device database, use the POST /v1/database/events, POST /v1/database/analytics, and POST /v1/database/tracks endpoints. Those are covered in the Querying Stored Data tutorial.

Next steps