> ## Documentation Index
> Fetch the complete documentation index at: https://docs.knowledgestack.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Real-Time Notifications

## Overview

Knowledge Stack provides two real-time communication channels:

* **WebSocket** -- For receiving instant notifications about events in your tenant (e.g., document ingestion completed)
* **Server-Sent Events (SSE)** -- For streaming AI assistant responses token-by-token

Both channels use Redis Streams as the underlying message transport, which enables horizontal scaling across multiple server instances.

## WebSocket Notifications

### Connecting

Connect to the WebSocket endpoint at:

```
WS /v1/ws
```

Authentication is handled automatically via your session cookie (`ks_uat`). On a successful connection, you will receive a confirmation message:

```json theme={null}
{
  "type": "system",
  "event": "connection_established",
  "connection_id": "abc-123",
  "user_id": "...",
  "tenant_id": "..."
}
```

### Subscribing to Channels

After connecting, subscribe to notification channels by sending JSON messages:

```json theme={null}
{"action": "subscribe", "channel": "ks:{tenant_id}:notifications"}
```

You can also subscribe to user-specific notifications:

```json theme={null}
{"action": "subscribe", "channel": "ks:{tenant_id}:users:{user_id}"}
```

To unsubscribe:

```json theme={null}
{"action": "unsubscribe", "channel": "ks:{tenant_id}:notifications"}
```

### Channel Security

Two security rules are enforced on all subscriptions:

1. The `tenant_id` in the channel must match your authenticated tenant
2. For user channels, the `user_id` must match your authenticated user

### Notification Events

| Event                          | Description                        | Key Fields                                          |
| ------------------------------ | ---------------------------------- | --------------------------------------------------- |
| `document_ingestion_completed` | A document has finished processing | `document_id`, `document_version_id`, `workflow_id` |

### Server Messages

| Event                    | Description                                        |
| ------------------------ | -------------------------------------------------- |
| `connection_established` | Connection accepted, includes your `connection_id` |
| `subscribed`             | Successfully subscribed to a channel               |
| `unsubscribed`           | Successfully unsubscribed from a channel           |
| `error`                  | An error occurred (includes `code` and `message`)  |

### Connection Close Codes

| Code | Meaning                                                |
| ---- | ------------------------------------------------------ |
| 4401 | Unauthorized -- invalid or missing session             |
| 4403 | Forbidden -- you are not a member of this tenant       |
| 4503 | Service unavailable -- real-time features are disabled |
| 1001 | Server shutting down                                   |

### Keepalive

The connection uses standard WebSocket ping/pong frames (RFC 6455) for keepalive. No application-level heartbeat is needed.

## SSE Thread Streaming

### Endpoint

```
GET /v1/threads/{thread_id}/stream
```

Authentication uses standard REST authentication (session cookie).

### Query Parameters

| Parameter         | Type   | Required | Description                                                                                      |
| ----------------- | ------ | -------- | ------------------------------------------------------------------------------------------------ |
| `last_message_id` | UUID   | No       | The ID of the last message you received. Used for reconnection.                                  |
| `last_entry_id`   | string | No       | The stream entry ID of the last event you received. Required when `last_message_id` is provided. |

### Streaming Events

When the AI assistant generates a response, you receive a sequence of SSE events:

| Event           | Description                                                   |
| --------------- | ------------------------------------------------------------- |
| `message_start` | A new assistant message is beginning                          |
| `text_start`    | A text segment is starting                                    |
| `text_delta`    | A chunk of text content (the `delta` field contains the text) |
| `text_end`      | The current text segment is complete                          |
| `step`          | A tool call or processing step occurred                       |
| `citations`     | Source citations for the response                             |
| `message_end`   | The assistant message is complete                             |
| `done`          | The stream is finished (`data: [DONE]`)                       |

The typical event lifecycle is:

```
message_start -> text_start -> text_delta* -> text_end -> citations? -> message_end -> done
```

Tool call `step` events may be interleaved during processing.

### SSE Event Format

Each event follows this format:

```
id: 1706000000000-5
event: text_delta
data: {"delta":"Hello","id":"msg-uuid","part_id":"part-uuid","ts":"2026-02-17T10:30:00Z","message_id":"msg-uuid"}
```

### Reconnection

If your connection drops while an assistant response is still streaming, you can reconnect with replay:

```
GET /v1/threads/{thread_id}/stream?last_message_id=abc&last_entry_id=1706000000000-5
```

Knowledge Stack will replay any events you missed and then continue with live events. You will receive a `replay_complete` event when replay is finished:

```
event: replay_complete
data: {"replayed_count":12,"last_entry_id":"1706000000000-50"}
```

If the message has already finished streaming when you reconnect, you will receive a `message_not_streaming` event instead. In that case, fetch the complete message via the REST API:

```
GET /v1/threads/{thread_id}/messages/{message_id}
```

### Best Practices for SSE

1. Open a new SSE connection when you navigate to a thread
2. For reconnection mid-stream, always include both `last_message_id` and `last_entry_id`
3. Do not rely on `EventSource` auto-reconnect -- close the connection and open a new one with updated parameters
4. The stream self-terminates when done. Open a new connection for the next message.

### SSE Keepalive

The server sends SSE comment pings (`: ping`) every 30 seconds to keep the connection alive.

## Configuration

If you are self-hosting Knowledge Stack, these settings control the real-time notification system:

| Setting         | Default    | Environment Variable    | Description                                                                |
| --------------- | ---------- | ----------------------- | -------------------------------------------------------------------------- |
| Redis URL       | `DISABLED` | `REDIS_URL`             | Redis connection string. Set to `DISABLED` to turn off real-time features. |
| Max connections | `20`       | `REDIS_MAX_CONNECTIONS` | Redis connection pool size                                                 |
| Stream TTL      | `30` min   | `WS_STREAM_TTL_MINUTES` | How long stream entries are retained                                       |
| SSE buffer size | `1024`     | `SSE_QUEUE_SIZE`        | Maximum buffered events per subscriber                                     |

## Error Handling

| Scenario                       | What Happens                                                              |
| ------------------------------ | ------------------------------------------------------------------------- |
| Redis is disabled              | WebSocket returns close code 4503. SSE returns an error.                  |
| Redis goes down during use     | The system automatically retries with exponential backoff                 |
| Your SSE client disconnects    | Your subscription is cleaned up automatically                             |
| SSE buffer fills up            | Your subscription is removed to prevent memory issues                     |
| Message is no longer streaming | SSE returns `message_not_streaming` -- fetch the message via REST instead |
