Signaling

Signaling

Subscribe to tunnel and client events over SSE or WebSocket.


Signaling is the real-time side of the engine API. The engine exposes the same stream over Server-Sent Events and over WebSocket, and the CLI and SDKs provide higher-level helpers on top of those raw transports.

Signaling serves a different purpose from the list APIs. The list APIs answer the point-in-time inventory question. Signaling carries that same inventory as an initial snapshot and then continues with incremental updates. This makes it suitable for fleet management, dashboards, automation controllers, and applications that need to react to resource changes without polling.

Engine endpoints and transports

The engine exposes an SSE endpoint at /api/sse and a WebSocket endpoint at /api/websocket. Both accept Authorization: Bearer <token> and also accept an authentication token through the rstream.token query parameter. Query authentication is intentionally limited to these streaming endpoints; list, token, and mutation APIs should use the Authorization header. The WebSocket endpoint supports the classic HTTP/1.1 upgrade path and Extended CONNECT over HTTP/2 or HTTP/3 when those transports are negotiated. The Go SDK and the CLI expose both sse and websocket transport values, and the JavaScript SDK watch helper uses the same transport model.

Browser EventSource and WebSocket clients cannot reliably attach custom Authorization headers, so browser watch integrations use the query parameter path. Treat these query tokens as runtime tokens: mint them on demand, keep them short-lived, and scope them to read-only tunnel listing. Do not persist a watch query token as durable session state.

If the backend route that mints the watch token can also see browser cookies, make the intended source credential explicit. For delegated flows, call the route with Authorization: Bearer <issuer-token> and omit ambient credentials so a logged-in dashboard session cannot accidentally mint a token for the wrong user.

For rstream.token query authentication, the engine accepts only watch-safe tokens:

  • endpoint must be /api/sse or /api/websocket;
  • token type must be auth or app; pat tokens are rejected;
  • token must include an expiration, with remaining TTL greater than zero and no more than one hour plus one minute;
  • token-level resources.tunnels boundaries must allow tunnels.list on every logical branch and must not allow tunnels.create or tunnels.connect;
  • if no token-level tunnel resource is present, broad permissions must allow resource reads and must not allow tunnel create/delete or stream create/delete operations.

The JavaScript Watch helper enforces the same shape before opening the browser connection and additionally requires a token lifetime of at most 3600 seconds from iat to exp.

Both transports send a state.initial event first, containing snapshot arrays of clients and tunnels, and then continue with incremental updates.

Filtering the stream

The signaling endpoints accept a params query parameter that applies server-side filters to both the initial snapshot and subsequent client.* and tunnel.* events. Client filters and tunnel filters are configured independently, which makes it possible to observe a subset of agents and a subset of tunnels in the same connection.

As with the list APIs, these filters are intended for the properties that vary inside a project. Labels are usually the most useful tunnel selectors, while client filters are typically built around agent metadata such as agent, channel, version, os, arch, or user_id. In practice, labels are often the most stable selectors because they follow service identity or environment segmentation rather than generated hostnames.

CLI usage

The CLI subscribes to the stream with rstream events. --client-filter and --tunnel-filter apply server-side filters. --events narrows the local output to selected event types after the filtered stream has been received. The command can emit newline-delimited JSON to stdout or forward selected watch events as HTTP POST requests to an external endpoint.

rstream events \
  --transport websocket \
  --client-filter 'agent=rstream,channel=dev' \
  --tunnel-filter 'labels.service=ssh,labels.env=prod'
rstream events \
  --transport sse \
  --events tunnel.created,tunnel.updated \
  --tunnel-filter 'labels.service=api' \
  --forward-to https://example.internal/hooks/rstream

Add --webhook when the forwarded request should use the webhook body and signed webhook headers:

rstream events \
  --webhook \
  --events tunnel.created,tunnel.deleted \
  --forward-to http://localhost:3000/api/rstream/webhook

The same filtering model is available in the Go and JavaScript SDKs through watch parameters. This keeps the selection model consistent across raw API calls, CLI inspection, and embedded application integrations. For the surrounding API model, see APIs; for label-driven selection, see Labels; and for SDK-specific helpers, see JS SDK and Go SDK.

For durable outbound notifications, use Webhooks. Webhooks deliver lifecycle events and keep delivery attempt history. They do not include traffic logs or stream.summary records.