STOMP Protocol Reference
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.
Every STOMP frame follows this structure:
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
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) |
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 |
SUBSCRIBE
id:sub-0
destination:/topic/change/a1b2c3d4-e5f6-7890-abcd-ef1234567890
\x00
The subscription id is client-defined. Use any unique string. If you create multiple subscriptions, each must have a distinct id.
DISCONNECT
Signals an intentional, clean disconnection.
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 |
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 |
MESSAGE
destination:/topic/change/a1b2c3d4-e5f6-7890-abcd-ef1234567890
message-id:msg-001
subscription:sub-0
{"entity":"POLICY_ALERT","entityUuid":"x1y2z3","type":"CREATE","timestampMs":1711843200000}
\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.
A heartbeat is a single newline character:
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
# 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
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:
Client Server
│ │
│ CONNECT │
│ accept-version:1.2 │
│ heart-beat:10000,10000 │
│ \x00 │
├─────────────────────────────────────►│
│ │
│ CONNECTED │
│ version:1.2 │
│ heart-beat:10000,0 │
│ \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.