> ## Documentation index
> Fetch the complete documentation index at: https://developer.rhombus.com/llms.txt
> Use it to discover all available pages before exploring further.

# WebSocket Authentication

> Authenticate the Rhombus event WebSocket: which credentials it accepts, where they go on the handshake, what a connection can see, partner connections, revocation, and how to read 401, 403 and 429 responses.

The event WebSocket (`wss://ws.rhombussystems.com:8443/websocket`) authenticates the **HTTP upgrade request** (the handshake). It accepts three credentials:

- An **API key**, standard or partner, in a request header.
- An **OAuth access token** with the `read` scope, as `Authorization: Bearer <token>`.
- A **federated token**, as headers or query parameters. This is the one a browser can use.

Certificate (mTLS) keys aren't accepted on this connection.

> **Note**: **Two different WebSockets.** This page is about the **event** WebSocket: one STOMP connection per organization that delivers alerts and device changes. Live video and audio also stream over WebSockets, but those are per-camera URLs returned by `getMediaUris` and they authenticate differently. See [Media and streams](https://developer.rhombus.com/authentication/media).

## Prerequisites

- A credential:
  - An API key created in the [Rhombus Console](https://console.rhombussystems.com/settings/integrations/api-tokens/) with **Auth Type: Api Token**. See [API keys and certificates](https://developer.rhombus.com/authentication/api-keys).
  - Or an OAuth access token from [Sign in with Rhombus](https://developer.rhombus.com/oauth-authentication) whose grant includes the `read` scope.
  - Or a [federated token](https://developer.rhombus.com/authentication/federated-tokens) minted by your backend.
- A client that can send that credential. API keys and OAuth tokens travel in request **headers**, and browsers can't set headers on a WebSocket handshake, so a web app either connects from its backend and relays events, or opens the socket in the browser with a federated token in the URL (see [Connecting from a browser](https://developer.rhombus.com/websocket/authentication#connecting-from-a-browser)).

> **Warning**: Certificate (mTLS) keys aren't accepted on the event WebSocket. If your REST integration uses a certificate key, create a standard API key (Auth Type **Api Token**) for the WebSocket, or mint a federated token.

## Generating an API token

1. Log in to the [Rhombus console](https://console.rhombussystems.com)
2. Go to **Settings → Integrations & Developer Resources → API Tokens** and click **Add API Key**
3. Enter a **Name**, set **Auth Type** to **Api Token**, and select a **Role**. The role decides which events the connection receives (see [What a connection receives](https://developer.rhombus.com/websocket/authentication#what-a-connection-receives)).
4. Copy and securely store the token

## Authentication parameters

Credentials go on the handshake (the HTTP upgrade request). Values sent later in the STOMP `CONNECT` frame (`login`, `passcode` or custom headers) are ignored.

| Credential         | `x-auth-scheme` (header or query parameter) | Credential goes in              | Where                     |
| ------------------ | ------------------------------------------- | ------------------------------- | ------------------------- |
| API key            | `api-token`                                 | `x-auth-apikey`                 | **Header only**           |
| OAuth access token | Leave it out, or `api-oauth-token`          | `Authorization: Bearer <token>` | **Header only**           |
| Federated token    | `federated-token`                           | `x-auth-ft`                     | Header or query parameter |

Partner credentials use the same schemes (`partner-api-token` and `partner-api-oauth-token` remain as aliases) and add one header:

| Name         | Where           | Value                          | Required                                                                                                                                                |
| ------------ | --------------- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `x-auth-org` | **Header only** | The client organization's UUID | Yes, for every partner credential (see [Partner API authentication](https://developer.rhombus.com/websocket/authentication#partner-api-authentication)) |

An API key or OAuth token sent as a query parameter isn't read, and the handshake fails with 401.

## Connection URL format

| Region | URL                                             |
| ------ | ----------------------------------------------- |
| US     | `wss://ws.rhombussystems.com:8443/websocket`    |
| EU     | `wss://ws.eu.rhombussystems.com:8443/websocket` |

Use port **8443** and TLS 1.2 or later. Credentials are region-bound like every Rhombus credential, so an EU key or token only works on the EU host. See [US & EU Regions](https://developer.rhombus.com/api-regions).

### API key

```text
wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token
```

Send the key in the `x-auth-apikey` header.

### OAuth access token

```text
wss://ws.rhombussystems.com:8443/websocket
```

Send `Authorization: Bearer <access token>`. A token whose grant doesn't include the `read` scope is refused with 403 `PERMISSION_REQUIRED` and `WWW-Authenticate: Bearer error="insufficient_scope", scope="read"`.

### Federated token

```text
wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=federated-token&x-auth-ft=TOKEN
```

The token has the role of the credential that minted it. If it was minted with `domain`, the handshake's `Origin` must match that origin, or it's refused with 403 `DOMAIN_NOT_ALLOWED`.

### Partner API token

```text
wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token
```

Send the partner key in `x-auth-apikey` and the client organization's UUID in the `x-auth-org` header. See [Partner API authentication](https://developer.rhombus.com/websocket/authentication#partner-api-authentication).

## Authentication flow

```text
1. Build the URL (x-auth-scheme as a query parameter or header)
         │
         ▼
2. Add the credential: x-auth-apikey, Authorization: Bearer, or x-auth-ft
   (and x-auth-org for partner credentials)
         │
         ▼
3. Open the WebSocket (wss, port 8443)
         │
    ┌────┴─────────────────────────┐
    ▼                              ▼
 Credentials accepted          Rejected before the upgrade:
    │                          401 (credential missing, unknown,
    ▼                               expired or revoked),
4. Send STOMP CONNECT          403 (valid credential, not allowed),
    │                          429 (rate limited)
    ▼
5. Receive STOMP CONNECTED → subscribe
```

No prior HTTP session is needed. The credential is checked at the handshake and again about once a minute while the connection is open (see [Revocation](https://developer.rhombus.com/websocket/authentication#revocation)).

## Retrieving your organization UUID

Topic names include your organization UUID. Retrieve it with the REST API:

```bash
curl -X POST https://api2.rhombussystems.com/api/org/getOrgV2 \
  -H "x-auth-scheme: api-token" \
  -H "x-auth-apikey: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{}'
```

The org UUID is at `org.uuid` in the response.

## Example: Authenticated connection

```python Python
import websocket

API_TOKEN = "YOUR_API_KEY"

url = "wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token"
headers = {
    "x-auth-apikey": API_TOKEN
}

ws = websocket.create_connection(url, header=headers)
print("WebSocket connected")
```

```javascript JavaScript (Node.js)
const WebSocket = require("ws");

const API_TOKEN = "YOUR_API_KEY";
const url = "wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token";

const ws = new WebSocket(url, {
  headers: {
    "x-auth-apikey": API_TOKEN,
  },
});

ws.on("open", () => {
  console.log("WebSocket connected");
});
```

```go Go
import (
    "fmt"
    "log"
    "net/http"
    "time"

    "github.com/gorilla/websocket"
)

apiToken := "YOUR_API_KEY"
url := "wss://ws.rhombussystems.com:8443/websocket?x-auth-scheme=api-token"

headers := http.Header{}
headers.Set("x-auth-apikey", apiToken)

dialer := websocket.Dialer{
    HandshakeTimeout: 10 * time.Second,
}

conn, _, err := dialer.Dial(url, headers)
if err != nil {
    log.Fatal("Connection failed:", err)
}
fmt.Println("WebSocket connected")
```

```python Python (OAuth)
import websocket

ACCESS_TOKEN = "YOUR_OAUTH_ACCESS_TOKEN"  # its grant must include the read scope

url = "wss://ws.rhombussystems.com:8443/websocket"
headers = {
    "Authorization": f"Bearer {ACCESS_TOKEN}"
}

ws = websocket.create_connection(url, header=headers)
print("WebSocket connected")
```

```javascript Browser (federated token)
// federatedToken comes from your backend; never put an API key in the page
const url = new URL("wss://ws.rhombussystems.com:8443/websocket");
url.searchParams.set("x-auth-scheme", "federated-token");
url.searchParams.set("x-auth-ft", federatedToken);

const ws = new WebSocket(url);
ws.onopen = () => console.log("WebSocket connected");
```

## Connecting from a browser

- **Use a federated token in the URL.** Your backend mints it with [`generateFederatedSessionToken`](https://developer.rhombus.com/api-reference/org-webservice/generate-federated-session-token) and hands it to the page, which opens the socket with `x-auth-scheme=federated-token&x-auth-ft=<token>` (see the browser example above). Mint it for the viewer's session (`durationSec` defaults to one hour and can't exceed 24 hours), read `expiresAtMs` from the response, and mint a new one before it expires. When the session ends, revoke it with `POST /api/org/revokeFederatedSessionToken`, from your backend or from the page, authenticated with the token itself. See [Federated tokens](https://developer.rhombus.com/authentication/federated-tokens).
- **Bind the token to your page with `domain`.** A token minted with `domain` (an absolute origin such as `https://app.example.com`) is accepted only when the handshake's `Origin` matches it.
- **A signed-in Rhombus console session only works from the Rhombus console.** A handshake that relies on the console's session cookies is accepted only from a Rhombus console origin, so a page on your own site can't use a viewer's console session. Handshakes with an API key, an OAuth token, a federated token or the Rhombus mobile app aren't origin-checked; a federated token's `domain` is its own origin check.

## What a connection receives

- **Events are filtered by the credential's role.** An API key uses its role. An OAuth token acts with the signed-in user's role, limited to the token's scopes. A federated token has the role of the credential that minted it. A full-access role receives every event in the organization. A role limited to some locations or devices receives only events for those; the rest are dropped without an error. If a connection is quiet, check the role before anything else.
- **Policy alerts follow the same rule.** A policy alert is delivered when the role can access the alert's device or its location. An alert with neither goes only to full-access roles.
- **Entity types without an access check go only to full-access roles.** A limited role receives the entity types the server can check against its devices and locations, and no others.
- **The connection is receive-only.** Clients subscribe; they don't send commands over it. Use the REST API to act.
- **Subscribing to another organization's topic is refused.** The server sends a STOMP `ERROR` frame and closes the socket with code `1002`.

## Partner API authentication

If you're a Rhombus partner receiving events for a client organization:

1. Use your partner key with the `api-token` scheme (`partner-api-token` works as an alias)
2. Send the client's `orgUuid` in the `x-auth-org` **header**. It's required: a partner credential without it is refused with 403 `PERMISSION_REQUIRED`. A blank value counts as absent, a query parameter isn't read, and a value that isn't a valid UUID gets 400 `INVALID_AUTH_ORG`.
3. Open one connection per client organization; `x-auth-org` is fixed for the life of the connection

```python
url = (
    "wss://ws.rhombussystems.com:8443/websocket"
    "?x-auth-scheme=api-token"
)
headers = {
    "x-auth-apikey": partner_api_token,
    "x-auth-org": client_org_uuid,
}

ws = websocket.create_connection(url, header=headers)
```

If your partner account isn't authorized for that client, the handshake fails with 403 `PARTNER_CLIENT_ACCESS_DENIED`.

## Revocation

Deleting a key, changing its role, revoking an OAuth grant, or revoking a federated token (or the credential that minted it) takes effect within 60 seconds. An open connection that used the credential receives a STOMP `ERROR` frame and is closed with code `1008`. After a role change, reconnecting picks up the new role. After a delete or revoke, every handshake with that credential fails with 401, so don't reconnect in a loop after a `1008`: check the credential first.

## Security best practices

- **Keep keys on your server.** Never ship an API key to a browser or mobile app; relay events from your backend, or give the browser a short-lived federated token.
- **Use a dedicated credential with the narrowest role** that still sees the devices you need. See [Permissions and key rotation](https://developer.rhombus.com/authentication/permissions).
- **Ask only for the OAuth scopes you need.** The event WebSocket needs `read`.
- **Reconnect with backoff.** Handshakes count toward your organization's [rate limit](https://developer.rhombus.com/rate-limits); a tight reconnect loop gets `429` responses.

## Troubleshooting

A handshake refused before the upgrade returns an HTTP status, an `x-rhombus-txid` header and the same JSON body as every Rhombus host (see [Errors](https://developer.rhombus.com/errors)):

```json
{
  "msg": "x-auth-org is required for partner credentials",
  "error": true,
  "errorCode": "PERMISSION_REQUIRED",
  "errorMsg": "Your role does not have the required permission on the requested resource.",
  "status": 403,
  "timestamp": 1791367200000,
  "txid": "6f1c2a9e4b7d"
}
```

Read `errorCode` for the reason. Quote `txid` when you contact support.

| Error                                                 | Cause                                                                                                                                                                            | Solution                                                                                                                                                 |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| HTTP 401 `UNAUTHENTICATED`                            | No credential reached the server: `x-auth-scheme` is missing, the key or OAuth token was sent as a query parameter, or the scheme is a certificate scheme (`api`, `partner-api`) | Send the scheme and the credential as the [table above](https://developer.rhombus.com/websocket/authentication#authentication-parameters) shows          |
| HTTP 401 `INVALID_CREDENTIAL`                         | The key or token is malformed, unknown (a deleted API key, for example), or from the other region                                                                                | Use an existing credential from the same region as the host                                                                                              |
| HTTP 401 `CREDENTIAL_EXPIRED` or `CREDENTIAL_REVOKED` | The key or token expired, or it was revoked (an OAuth token whose grant was revoked, for example)                                                                                | Create a new key, refresh or re-authorize the OAuth token, or mint a new federated token                                                                 |
| HTTP 401 `CERTIFICATE_MISMATCH`                       | A certificate key was sent with `api-token`                                                                                                                                      | Use a standard API key (Auth Type **Api Token**)                                                                                                         |
| HTTP 403 `PERMISSION_REQUIRED`                        | A partner credential without `x-auth-org`, or an OAuth token without the `read` scope (the response names the scope in `WWW-Authenticate`)                                       | Send `x-auth-org`; ask the user to authorize the `read` scope                                                                                            |
| HTTP 403 `PARTNER_CLIENT_ACCESS_DENIED`               | The partner isn't authorized for the `x-auth-org` organization                                                                                                                   | Check the partner's access to the client                                                                                                                 |
| HTTP 403 `DOMAIN_NOT_ALLOWED`                         | A federated token minted with `domain`, opened from another origin                                                                                                               | Mint the token with your page's exact origin                                                                                                             |
| HTTP 400 `INVALID_AUTH_ORG`                           | `x-auth-org` isn't a valid organization UUID                                                                                                                                     | Send the client organization's UUID                                                                                                                      |
| HTTP 429 `RATE_LIMITED`                               | Too many handshakes or API calls for the organization                                                                                                                            | Wait for `Retry-After` seconds, then reconnect with backoff                                                                                              |
| Connected, but no events arrive                       | The credential's role doesn't cover the devices or locations                                                                                                                     | Use a role that covers the devices (see [What a connection receives](https://developer.rhombus.com/websocket/authentication#what-a-connection-receives)) |
| STOMP `ERROR` and close code `1008`                   | The credential was deleted, revoked or had its role changed while connected                                                                                                      | Check the credential before reconnecting (see [Revocation](https://developer.rhombus.com/websocket/authentication#revocation))                           |
| STOMP `ERROR` and close code `1002`                   | Subscribed to a topic outside the credential's organization, or sent a frame the server doesn't accept                                                                           | Subscribe with the organization UUID the credential belongs to (or the `x-auth-org` client)                                                              |
| Connection drops after about 30 seconds               | No heartbeats                                                                                                                                                                    | Send STOMP heartbeats (see [Connection Lifecycle](https://developer.rhombus.com/websocket/connection-lifecycle))                                         |
| Connection timeout                                    | Network or firewall                                                                                                                                                              | Allow outbound TLS to port 8443                                                                                                                          |
