WebSocket Stream
The dashboard’s live tail is powered by a single WebSocket endpoint:
ws://127.0.0.1:8080/api/v1/wsThe wire format is JSON. Each frame is a self-describing message with a type discriminator.
Connecting
Section titled “Connecting”const ws = new WebSocket('ws://127.0.0.1:8080/api/v1/ws');
ws.onmessage = (frame) => { const msg = JSON.parse(frame.data); switch (msg.type) { case 'event': /* ... */ break; case 'alert': /* ... */ break; case 'status': /* ... */ break; }};The server pushes; the client does not need to subscribe — every connected client receives every event and alert. Filtering happens client-side in the widgets.
Message types
Section titled “Message types”Emitted whenever a new event is committed to storage.
{ "type": "event", "event": { "id": "01HZX...", "observed_ts": "2026-05-13T15:30:00.123Z", "event_ts": "2026-05-13T15:29:59.998Z", "source": "syslog", "severity": 13, "message": "Failed password for root from 10.0.1.42", "template_id": "drain_0042", "template": "Failed password for <*> from <*>", "entities": [ { "type": "user", "value": "root", "uuid": "..." }, { "type": "ipv4", "value": "10.0.1.42", "uuid": "..." } ], "anomaly_score": 0.87, "anomaly_threshold": 0.63, "attack": { "tactic": "credential-access", "technique": "T1110.001" } }}Emitted when an alert fires — Sigma match, blended-score breach, kill-chain detection, UEBA, threat-intel match, or risk-accumulation threshold.
{ "type": "alert", "alert": { "id": "alr_01HZX...", "ts": "2026-05-13T15:30:00.456Z", "rule_id": "ssh_brute_force", "rule_kind": "sigma", "severity": "high", "title": "SSH brute force", "entities": [ { "type": "ipv4", "value": "10.0.1.42", "uuid": "..." } ], "attack": { "tactic": "credential-access", "technique": "T1110.001" }, "dedup_key": "sigma:ssh_brute_force:10.0.1.42" }}status
Section titled “status”Periodic pipeline heartbeat — surfaces throughput, queue depth, and degraded subsystems. The dashboard uses it to drive the disconnected banner and stats card.
{ "type": "status", "ts": "2026-05-13T15:30:01.000Z", "throughput_eps": 4287, "queue_depth": 23, "queue_max": 10000, "receivers": { "syslog": "ok", "otlp_grpc": "ok", "file": "ok" }, "detectors": { "hst": "ok", "holt_winters": "ok", "cusum": "ok", "markov": "ok" }}Reconnect strategy
Section titled “Reconnect strategy”The bundled dashboard reconnects with exponential backoff (250 ms → 8 s, capped) and shows the red Disconnected banner until the next message arrives. Build the same pattern into any custom client — there is no message replay, so reconnecting clients only receive frames sent after they are subscribed.
Backfill
Section titled “Backfill”When a widget first mounts it issues a normal REST call (GET /api/v1/alerts?since=…, GET /api/v1/events?since=…) to populate history, then transitions to the WebSocket for live tail. See the REST API for the backfill endpoints.