WebSocket
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.
On this page
- What is STOMP?
- Frame format
- Example: Raw CONNECT frame
- Client-to-server frames
- CONNECT
- SUBSCRIBE
- DISCONNECT
- Server-to-client frames
- CONNECTED
- MESSAGE
- Heartbeats
- Format
- Negotiation
- Behavior
- Implementation
- Frame parsing guide
- Step-by-step parsing algorithm
- Edge cases
- Complete frame exchange
- Differences from full STOMP 1.2
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:
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:valuepairs, 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
\x00Client-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
\x00SUBSCRIBE
Subscribes to a destination topic to receive messages.
| Header | Required | Value | Description |
|---|---|---|---|
id | Yes | sub-0 | Client-defined subscription identifier |
destination | Yes | /topic/ | Topic path to subscribe to |
SUBSCRIBE
id:sub-0
destination:/topic/change/a1b2c3d4-e5f6-7890-abcd-ef1234567890
\x00The 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.
DISCONNECT
\x00No 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
\x00MESSAGE
Delivers an event from a subscribed topic.
| Header | Value | Description |
|---|---|---|
destination | /topic/ | 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"}
\x00The 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:
\nNo 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 heartbeatscy= 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 frameFrame 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,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 | Sent before the server closes the socket, for example with code 1008 when the credential is revoked or its role changes, 1002 for a refused subscription or frame |
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.