> ## 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.

# WebSocket Connection Lifecycle

> Manage the full lifecycle of a Rhombus WebSocket connection — perform STOMP handshakes, send heartbeats, handle reconnections, and close gracefully.

This guide covers the full lifecycle of a Rhombus WebSocket connection: establishing the connection, performing the STOMP handshake, maintaining the connection with heartbeats, and handling disconnections gracefully.

## Lifecycle Overview

```text theme={null}
┌─────────────────┐
│  1. WebSocket    │
│     Handshake    │──── WSS + Auth Headers
└────────┬────────┘
         ▼
┌─────────────────┐
│  2. STOMP        │
│     CONNECT      │──── Version + Heartbeat negotiation
└────────┬────────┘
         ▼
┌─────────────────┐
│  3. SUBSCRIBE    │──── Topic subscription
└────────┬────────┘
         ▼
┌─────────────────┐
│  4. MESSAGE      │◄─── Real-time events
│     Loop         │───► Heartbeat ping/pong
└────────┬────────┘
         ▼
┌─────────────────┐
│  5. DISCONNECT   │──── Graceful shutdown
└─────────────────┘
```

## Step 1: WebSocket Handshake

Initiate a secure WebSocket connection to the Rhombus endpoint with authentication headers:

```python theme={null}
import websocket

ws = websocket.create_connection(
    "wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token",
    header={"x-auth-apikey": API_TOKEN},
    timeout=10  # 10-second handshake timeout
)
```

A successful handshake upgrades the HTTP connection to WebSocket. If authentication fails, the server returns an HTTP error (401/403) before the upgrade completes.

## Step 2: STOMP CONNECT

After the WebSocket connection is established, send a STOMP `CONNECT` frame to initialize the messaging protocol:

### CONNECT Frame

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

\x00
```

| Header           | Value         | Description                                            |
| ---------------- | ------------- | ------------------------------------------------------ |
| `accept-version` | `1.2`         | STOMP protocol version                                 |
| `heart-beat`     | `10000,10000` | Client send/receive heartbeat interval in milliseconds |

### Expected Response: CONNECTED Frame

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

\x00
```

The server echoes back the negotiated version and heartbeat intervals.

### Building STOMP Frames

STOMP frames follow a simple text-based format:

```text theme={null}
COMMAND\n
header1:value1\n
header2:value2\n
\n
body (optional)
\x00
```

* Each frame starts with a command name
* Headers are key-value pairs separated by colons, one per line
* An empty line separates headers from the body
* The frame ends with a null byte (`\x00`)

<CodeGroup>
  ```python Python theme={null}
  def build_stomp_frame(command, headers=None, body=""):
      """Construct a STOMP protocol frame."""
      frame = command + "\n"
      if headers:
          for key, value in headers.items():
              frame += f"{key}:{value}\n"
      frame += "\n"
      frame += body
      frame += "\x00"
      return frame

  # Send CONNECT
  connect_frame = build_stomp_frame("CONNECT", {
      "accept-version": "1.2",
      "heart-beat": "10000,10000"
  })
  ws.send(connect_frame)

  # Read CONNECTED response
  response = ws.recv()
  ```

  ```javascript JavaScript theme={null}
  function buildStompFrame(command, headers = {}, body = "") {
    let frame = command + "\n";
    for (const [key, value] of Object.entries(headers)) {
      frame += `${key}:${value}\n`;
    }
    frame += "\n";
    frame += body;
    frame += "\x00";
    return frame;
  }

  // Send CONNECT
  ws.send(
    buildStompFrame("CONNECT", {
      "accept-version": "1.2",
      "heart-beat": "10000,10000",
    })
  );
  ```

  ```go Go theme={null}
  func buildStompFrame(command string, headers map[string]string, body string) string {
      var b strings.Builder
      b.WriteString(command + "\n")
      for k, v := range headers {
          b.WriteString(fmt.Sprintf("%s:%s\n", k, v))
      }
      b.WriteString("\n")
      b.WriteString(body)
      b.WriteByte(0x00)
      return b.String()
  }

  // Send CONNECT
  connectFrame := buildStompFrame("CONNECT", map[string]string{
      "accept-version": "1.2",
      "heart-beat":     "10000,10000",
  }, "")
  conn.WriteMessage(websocket.TextMessage, []byte(connectFrame))
  ```
</CodeGroup>

## Step 3: Subscribe to a Topic

After the STOMP connection is established, subscribe to the organization change topic:

### SUBSCRIBE Frame

```text theme={null}
SUBSCRIBE
id:sub-0
destination:/topic/change/{orgUuid}

\x00
```

| Header        | Value                     | Description                              |
| ------------- | ------------------------- | ---------------------------------------- |
| `id`          | `sub-0`                   | A client-defined subscription identifier |
| `destination` | `/topic/change/{orgUuid}` | The topic to subscribe to                |

```python theme={null}
subscribe_frame = build_stomp_frame("SUBSCRIBE", {
    "id": "sub-0",
    "destination": f"/topic/change/{org_uuid}"
})
ws.send(subscribe_frame)
```

<Note>
  Replace `{orgUuid}` with your organization's UUID. Retrieve it via the REST API endpoint `POST /api/org/getOrgV2`.
</Note>

## Step 4: Message Loop and Heartbeats

Once subscribed, your application enters a message loop where it:

1. **Receives MESSAGE frames** containing event data
2. **Sends heartbeats** to keep the connection alive
3. **Receives heartbeats** from the server

### Heartbeat Mechanism

Heartbeats are single newline characters (`\n`) sent at the negotiated interval (10 seconds). Both the client and server send heartbeats. If either side stops receiving heartbeats, the connection is considered dead.

```python theme={null}
import threading
import time

def send_heartbeats(ws, stop_event):
    """Send STOMP heartbeats every 10 seconds."""
    while not stop_event.is_set():
        try:
            ws.send("\n")
        except Exception:
            break
        stop_event.wait(10)

# Start heartbeat sender in background
stop_heartbeat = threading.Event()
heartbeat_thread = threading.Thread(
    target=send_heartbeats,
    args=(ws, stop_heartbeat),
    daemon=True
)
heartbeat_thread.start()
```

### Parsing Incoming Frames

```python theme={null}
def parse_stomp_frame(raw):
    """Parse a raw STOMP frame into command, headers, and body."""
    # Handle heartbeats (empty frames)
    raw = raw.lstrip("\n\r")
    if not raw or raw == "\x00":
        return None  # Heartbeat

    raw = raw.rstrip("\x00")
    parts = raw.split("\n\n", 1)

    lines = parts[0].split("\n")
    command = lines[0]

    headers = {}
    for line in lines[1:]:
        if ":" in line:
            key, value = line.split(":", 1)
            headers[key] = value

    body = parts[1] if len(parts) > 1 else ""
    return {"command": command, "headers": headers, "body": body}
```

### Message Loop

```python theme={null}
try:
    while True:
        raw = ws.recv()
        frame = parse_stomp_frame(raw)

        if frame is None:
            continue  # Heartbeat, skip

        if frame["command"] == "MESSAGE":
            payload = json.loads(frame["body"])
            handle_event(payload)

except KeyboardInterrupt:
    print("Shutting down...")
finally:
    stop_heartbeat.set()
    heartbeat_thread.join()
```

## Step 5: Graceful Disconnection

Before closing the WebSocket, send a STOMP `DISCONNECT` frame:

```python theme={null}
# Send DISCONNECT frame
disconnect_frame = build_stomp_frame("DISCONNECT")
ws.send(disconnect_frame)

# Close WebSocket
ws.close()
```

This signals to the server that the client is intentionally disconnecting, allowing it to clean up resources immediately rather than waiting for a heartbeat timeout.

## Reconnection Strategy

Network interruptions are inevitable. Implement automatic reconnection with backoff:

```python theme={null}
import time

def connect_with_retry(api_token, org_uuid, max_retries=10):
    """Connect to Rhombus WebSocket with exponential backoff."""
    for attempt in range(max_retries):
        try:
            ws = websocket.create_connection(
                "wss://ws.rhombussystems.com:8443/websocket"
                "?x-auth-scheme=api-token",
                header={"x-auth-apikey": api_token},
                timeout=10
            )
            stomp_connect(ws)
            stomp_subscribe(ws, org_uuid)
            print("Connected successfully")
            return ws
        except Exception as e:
            delay = min(5 * (2 ** attempt), 60)
            print(f"Connection failed: {e}. Retrying in {delay}s...")
            time.sleep(delay)

    raise ConnectionError("Max retries exceeded")
```

### Full Reconnection Loop

```python theme={null}
while True:
    ws = connect_with_retry(API_TOKEN, ORG_UUID)

    try:
        run_message_loop(ws)
    except websocket.WebSocketConnectionClosedException:
        print("Connection lost. Reconnecting...")
    except KeyboardInterrupt:
        print("User interrupted. Exiting.")
        break
    finally:
        try:
            ws.close()
        except Exception:
            pass
```

## Connection Timeouts

| Timeout         | Value        | Description                                                        |
| --------------- | ------------ | ------------------------------------------------------------------ |
| Handshake       | 10 seconds   | Maximum time to complete the WebSocket upgrade                     |
| Heartbeat       | 10 seconds   | Interval between keep-alive pings                                  |
| Dead connection | \~30 seconds | Approximate time before a missing heartbeat triggers disconnection |

## Common Issues

| Symptom                          | Likely Cause                    | Solution                                              |
| -------------------------------- | ------------------------------- | ----------------------------------------------------- |
| Connection drops every \~30s     | Heartbeats not being sent       | Implement the heartbeat sender thread                 |
| `CONNECTED` frame never received | STOMP `CONNECT` frame malformed | Verify frame format includes null terminator `\x00`   |
| Messages not received            | Wrong topic or missing org UUID | Verify org UUID matches your API token's organization |
| Reconnection loop                | Token expired or revoked        | Check token validity via REST API                     |
