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

# STOMP Protocol Reference

> Reference for the STOMP 1.2 messaging protocol used by Rhombus WebSocket — frame structure, headers, CONNECT, SUBSCRIBE, MESSAGE, and ERROR command details.

Rhombus uses **STOMP 1.2** (Streaming Text Oriented Messaging Protocol) over WebSocket for real-time event delivery. This page is a protocol reference for developers who need to understand or implement the STOMP framing at a low level.

## What is STOMP?

STOMP is a simple, text-based messaging protocol that provides an interoperable wire format for message brokers. It defines a small set of commands (frames) for connecting, subscribing, sending, and disconnecting. Rhombus uses STOMP because it provides structured pub/sub messaging over a standard WebSocket transport.

## Frame Format

Every STOMP frame follows this structure:

```text theme={null}
COMMAND\n
header-key:header-value\n
header-key:header-value\n
\n
body (optional)
\x00
```

* **COMMAND**: A single word identifying the frame type (e.g., `CONNECT`, `MESSAGE`)
* **Headers**: Zero or more `key:value` pairs, one per line
* **Empty line**: Separates headers from body
* **Body**: Optional payload (typically JSON for MESSAGE frames)
* **Null byte** (`\x00`): Terminates every frame

### Example: Raw CONNECT Frame

```text theme={null}
CONNECT\n
accept-version:1.2\n
heart-beat:10000,10000\n
\n
\x00
```

## Client-to-Server Frames

### CONNECT

Initiates the STOMP session after the WebSocket connection is open.

| Header           | Required | Value         | Description                                            |
| ---------------- | -------- | ------------- | ------------------------------------------------------ |
| `accept-version` | Yes      | `1.2`         | STOMP protocol version                                 |
| `heart-beat`     | Yes      | `10000,10000` | Client send interval and desired receive interval (ms) |

```text theme={null}
CONNECT
accept-version:1.2
heart-beat:10000,10000

\x00
```

### SUBSCRIBE

Subscribes to a destination topic to receive messages.

| Header        | Required | Value                     | Description                            |
| ------------- | -------- | ------------------------- | -------------------------------------- |
| `id`          | Yes      | `sub-0`                   | Client-defined subscription identifier |
| `destination` | Yes      | `/topic/change/{orgUuid}` | Topic path to subscribe to             |

```text theme={null}
SUBSCRIBE
id:sub-0
destination:/topic/change/a1b2c3d4-e5f6-7890-abcd-ef1234567890

\x00
```

<Note>
  The subscription `id` is client-defined. Use any unique string. If you create multiple subscriptions, each must have a distinct `id`.
</Note>

### DISCONNECT

Signals an intentional, clean disconnection.

```text theme={null}
DISCONNECT

\x00
```

No headers or body are required. After sending DISCONNECT, close the underlying WebSocket connection.

## Server-to-Client Frames

### CONNECTED

Sent by the server in response to a successful CONNECT.

| Header       | Value         | Description                             |
| ------------ | ------------- | --------------------------------------- |
| `version`    | `1.2`         | Negotiated STOMP version                |
| `heart-beat` | `10000,10000` | Server send/receive heartbeat intervals |

```text theme={null}
CONNECTED
version:1.2
heart-beat:10000,10000

\x00
```

### MESSAGE

Delivers an event from a subscribed topic.

| Header         | Value                     | Description                       |
| -------------- | ------------------------- | --------------------------------- |
| `destination`  | `/topic/change/{orgUuid}` | The topic this message belongs to |
| `message-id`   | Server-generated          | Unique message identifier         |
| `subscription` | `sub-0`                   | Matches the subscription `id`     |

```text theme={null}
MESSAGE
destination:/topic/change/a1b2c3d4-e5f6-7890-abcd-ef1234567890
message-id:msg-001
subscription:sub-0

{"entity":"POLICY_ALERT","entityUuid":"x1y2z3","type":"CREATE"}
\x00
```

The body is always a JSON object containing the event payload.

## Heartbeats

Heartbeats keep the connection alive and detect failures. They are negotiated during the CONNECT/CONNECTED exchange.

### Format

A heartbeat is a single newline character:

```text theme={null}
\n
```

No command, no headers, no null terminator. Just `\n`.

### Negotiation

The `heart-beat` header uses the format `cx,cy` where:

* `cx` = minimum interval (ms) at which the client can send heartbeats
* `cy` = desired interval (ms) at which the client wants to receive heartbeats

The Rhombus server uses `10000,10000` (10 seconds both directions).

### Behavior

| Direction        | Interval   | What Happens on Failure                                   |
| ---------------- | ---------- | --------------------------------------------------------- |
| Client to Server | 10 seconds | Server may close the connection                           |
| Server to Client | 10 seconds | Client should assume the connection is dead and reconnect |

### Implementation

```python theme={null}
# Sending heartbeats
import threading

def send_heartbeats(ws, stop_event, interval=10):
    while not stop_event.is_set():
        ws.send("\n")
        stop_event.wait(interval)

# Receiving heartbeats
def parse_frame(raw):
    raw = raw.strip("\n\r")
    if not raw or raw == "\x00":
        return None  # This is a heartbeat, safe to ignore
    # ... parse as normal STOMP frame
```

## Frame Parsing Guide

### Step-by-Step Parsing Algorithm

```text theme={null}
1. Read WebSocket text message
2. Strip leading newlines/carriage returns
3. If empty or just \x00 → heartbeat, return null
4. Strip trailing \x00
5. Split on "\n\n" (first occurrence) → [header_block, body]
6. Split header_block on "\n" → [command, header1, header2, ...]
7. For each header line, split on first ":" → key, value
8. Return {command, headers, body}
```

### Edge Cases

| Input                       | Result                      |
| --------------------------- | --------------------------- |
| `\n`                        | Heartbeat (null)            |
| `\n\n\n`                    | Heartbeat (null)            |
| `\x00`                      | Heartbeat (null)            |
| Frame with no body          | Body is empty string        |
| Header value containing `:` | Split only on *first* colon |

## Complete Frame Exchange

Here is the full frame sequence for a typical session:

```text theme={null}
Client                                Server
  │                                      │
  │  CONNECT                             │
  │  accept-version:1.2                  │
  │  heart-beat:10000,10000              │
  │  \x00                               │
  ├─────────────────────────────────────►│
  │                                      │
  │                          CONNECTED   │
  │                          version:1.2 │
  │              heart-beat:10000,10000 │
  │                                \x00  │
  │◄─────────────────────────────────────┤
  │                                      │
  │  SUBSCRIBE                           │
  │  id:sub-0                            │
  │  destination:/topic/change/{org}     │
  │  \x00                               │
  ├─────────────────────────────────────►│
  │                                      │
  │  \n  (heartbeat)                     │
  ├─────────────────────────────────────►│
  │                                      │
  │                     \n  (heartbeat)  │
  │◄─────────────────────────────────────┤
  │                                      │
  │                            MESSAGE   │
  │            destination:/topic/...    │
  │                                      │
  │                  {"entity":"..."}    │
  │                                \x00  │
  │◄─────────────────────────────────────┤
  │                                      │
  │  \n  (heartbeat)                     │
  ├─────────────────────────────────────►│
  │                                      │
  │  DISCONNECT                          │
  │  \x00                               │
  ├─────────────────────────────────────►│
  │                                      │
  │           [WebSocket close]          │
  │◄─────────────────────────────────────┤
```

## Differences from Full STOMP 1.2

The Rhombus implementation uses a subset of STOMP 1.2. Notable differences:

| Feature                 | STOMP 1.2 Spec | Rhombus Implementation       |
| ----------------------- | -------------- | ---------------------------- |
| `SEND` command          | Supported      | Not used (server-push only)  |
| `UNSUBSCRIBE`           | Supported      | Not needed (single topic)    |
| `ACK`/`NACK`            | Supported      | Not used (auto-acknowledge)  |
| `RECEIPT`               | Supported      | Not used                     |
| `ERROR` frame           | Supported      | May be sent on server errors |
| `content-length` header | Optional       | Not required                 |
| Transaction support     | Supported      | Not used                     |

This means you do **not** need a full STOMP client library. The protocol subset is simple enough to implement manually, as shown in the [Code Examples](/websocket/code-examples).
