---
title: Events and Real-time Updates
path: core-concepts/events-and-realtime
status: published
---

ScaiDrive exposes change events two ways: a persistent per-tenant **change log** that drives the cursor-based sync protocol, and a **WebSocket** stream for low-latency notifications. They share the same underlying data — the WebSocket is a tail of the log, the polling API is a seek-and-read.

## The change log

Every mutation on a tenant writes exactly one row to `ChangeLog`:

```json
{
  "id": "chg_01J3L",
  "resource_type": "file",
  "resource_id": "fil_01J3M",
  "change_type": "updated",
  "changed_by": "usr_01J3N",
  "changed_at": "2026-04-23T10:15:02Z",
  "metadata": {"size": 12345, "mime_type": "application/pdf"},
  "old_path": "/Engineering/spec.pdf",
  "new_path": "/Engineering/spec.pdf",
  "version_id": "ver_01J3P"
}
```

Change types: `created`, `updated`, `deleted`, `moved`, `renamed`.
Resource types: `file`, `folder`, `share`.

The log is append-only and ordered. It's consumed via `GET /api/v1/sync/changes` — see [Sync Model](/docs/scaidrive/core-concepts/sync-model).

## Real-time WebSocket

Two WebSocket endpoints:

| Endpoint | Purpose |
|----------|---------|
| `/api/v1/sync/ws/{device_id}?token=<JWT>` | Per-device sync nudges. For sync clients |
| `/api/v1/realtime/ws?token=<JWT>` | General real-time updates for the authenticated user. For UIs |

Both authenticate via a `token` query parameter (the JWT) since browsers can't set custom headers on WebSocket connections.

## Sync WebSocket

Used by clients that replicate content locally. A sync client connects, subscribes to its shares implicitly (based on user access), and receives nudges:

```json
{"event_type": "change",                    "share_id": "shr_...", "data": {change entry}}
{"event_type": "sync_preference_updated",   "share_id": "shr_...", "device_id": "dev_..."}
{"event_type": "force_sync",                "share_id": "shr_..."}
{"event_type": "pong"}
```

Client-to-server messages:

```json
{"type": "ping"}
{"type": "subscribe"}
```

The nudge tells the client "something happened" — the client still goes through the cursor-based pull to fetch changes. The WebSocket is not the source of truth.

## Realtime WebSocket (UI)

Used by web and mobile UIs. Subscribe explicitly to shares you want updates for:

```javascript
const ws = new WebSocket(`wss://scaidrive.scailabs.ai/api/v1/realtime/ws?token=${token}`);

ws.onopen = () => {
  ws.send(JSON.stringify({ action: "subscribe", share_id: "shr_01J3K" }));
};

ws.onmessage = (evt) => {
  const frame = JSON.parse(evt.data);
  if (frame.type === "file_updated") {
    refreshFileRow(frame.data.file_id);
  }
};
```

```python
import asyncio, json
import websockets

async def watch(url, token, share_id):
    async with websockets.connect(f"{url}/api/v1/realtime/ws?token={token}") as ws:
        await ws.send(json.dumps({"action": "subscribe", "share_id": share_id}))
        async for msg in ws:
            frame = json.loads(msg)
            print(frame["type"], frame.get("data"))

asyncio.run(watch("wss://scaidrive.scailabs.ai", token, "shr_01J3K"))
```

Server-to-client frame types:

| Frame | Payload |
|-------|---------|
| `connected` | `{user_id, timestamp}` |
| `subscribed` | `{share_id}` |
| `unsubscribed` | `{share_id}` |
| `file_created` | `{share_id, data: file metadata}` |
| `file_updated` | `{share_id, data: file metadata}` |
| `file_deleted` | `{share_id, data: {file_id}}` |
| `folder_created` | `{share_id, data: folder metadata}` |
| `folder_deleted` | `{share_id, data: {folder_id}}` |
| `pong` | response to ping |

Client-to-server actions: `subscribe`, `unsubscribe`, `ping`.

Unlike the sync WebSocket, realtime frames carry full resource metadata in `data` so a UI can update without a re-fetch.

See [Real-time WebSocket](/docs/scaidrive/advanced/realtime-websocket) for reconnection, heartbeats, and back-pressure handling.

## ScaiKey webhooks (inbound)

ScaiDrive receives webhooks from ScaiKey to keep user and group records in sync. These are inbound to the ScaiDrive server; you don't configure them from your application.

```http
POST /api/v1/webhooks/scaikey
```

Events:

- `user.created`, `user.updated`, `user.deleted`
- `group.created`, `group.updated`, `group.deleted`

ScaiDrive verifies the signature header set by ScaiKey before processing.

## Outbound webhooks

Outbound webhooks — ScaiDrive calling *your* endpoint — are not currently available in the core API. For now, use the WebSocket for real-time or `GET /api/v1/sync/changes` for scheduled polling. If your use case is SIEM integration or compliance, see [Enterprise Compliance](/docs/scaidrive/advanced/enterprise-compliance) for the audit-event streaming options.

## Audit events

Distinct from the change log: **audit events** record who did what, including non-mutating actions (logins, downloads, permission checks). Audit events are exposed through the enterprise audit API and forwarded to SIEM integrations when configured.

```json
{
  "id": "aud_01J3L",
  "tenant_id": "tnt_01J3A",
  "event_type": "file.create",
  "category": "file_modification",
  "severity": "info",
  "user_id": "usr_01J3N",
  "user_email": "alice@example.com",
  "service_account": "3seej56cg90454l845dt93rfilywskbw",
  "resource_type": "file",
  "resource_id": "fil_01J3M",
  "resource_name": "scaispeak_job_42.mp3",
  "share_id": "shr_01J3K",
  "action": "create",
  "outcome": "success",
  "ip_address": "203.0.113.7",
  "user_agent": "ScaiSpeak/1.0",
  "details": {"version": 1, "size": 22300000},
  "event_time": "2026-04-23T10:15:05Z"
}
```

The `category` field is one of `authentication`, `authorization`, `file_access`, `file_modification`, `sharing`, `admin`, `security`, `compliance`. The `service_account` field carries the RFC 8693 `act.client_id` from the JWT when the request reached us through a token-exchange flow — null otherwise. See [Enterprise Compliance](/docs/scaidrive/advanced/enterprise-compliance#categories) for the full event-type vocabulary and severity escalation rules.

## What's next

- [Sync Model](/docs/scaidrive/core-concepts/sync-model) — the cursor protocol that consumes the change log.
- [Real-time WebSocket](/docs/scaidrive/advanced/realtime-websocket) — advanced WebSocket usage.
- [Enterprise Compliance](/docs/scaidrive/advanced/enterprise-compliance) — audit and SIEM.