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 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 watch integrations normally use the query parameter path. Treat that query token as a runtime credential: mint it on demand, keep it short-lived, and never store it as durable browser session state.
Browser query authentication
The watch stream is easy to confuse with published tunnel token authentication because both can carry a value named rstream.token. They are different checks. A watch token authenticates the browser to the engine inventory stream. A tunnel traffic token authorizes a downstream request to one published tunnel. A producer token authorizes an agent or SDK to create a tunnel. Those token builders should stay separate in application code.
For /api/sse and /api/websocket, the engine accepts only watch-safe query tokens:
- endpoint must be
/api/sseor/api/websocket; - token type must be
authorapp;pattokens 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.tunnelsboundaries must allowtunnels.liston every logical branch and must not allowtunnels.createortunnels.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.
In application integrations, prefer the explicit watch-token shape even when a broader token would pass the engine check:
{
"permissions": ["tunnels.resources.read-only"],
"resources": {
"tunnels": {
"scopes": {
"tunnels": {
"list": true
}
}
}
}
}Add project, workspace, label, or field selectors to that resources.tunnels boundary when the browser should see only part of the inventory. Do not add create or connect; producer tokens and viewer tokens have their own narrower purpose. The JavaScript Watch helper enforces the same watch shape before opening the browser connection and additionally requires a token lifetime of at most 3600 seconds from iat to exp.
If the backend route that mints the watch token can be authenticated by multiple mechanisms, 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 principal or project.
Both transports send a state.initial event first, containing snapshot arrays of clients and tunnels, and then continue with incremental updates.
Events are ordered within one watch connection. Consumers treat a reconnect as a new subscription, read the next state.initial snapshot, and rebuild local state from that snapshot before applying later incremental events. Webhooks are the durable notification surface; the signaling stream is a live inventory and control-plane observation surface.
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/rstreamAdd --webhook when the forwarded request uses the webhook body and
signed webhook headers:
rstream events \
--webhook \
--events tunnel.created,tunnel.deleted \
--forward-to http://localhost:3000/api/rstream/webhookThe 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.