Core Features
WebSocket Capture
Reliable instruments window.WebSocket to record one summary row per connection — lifecycle, message and byte counts, reconnect storms — and forwards abnormal closes and post-open errors into the errors pipeline.
How it works#
On init(), Reliable wraps window.WebSocket. Every connection created from that point is observed: the open timestamp, every message event (counted, not stored), every error, the close code, and outbound send() calls.
Counters live in memory for the lifetime of the socket. When the connection closes — cleanly or otherwise — a single POST /websocket event is emitted with the full summary. One row per connection, never one per message.
Why one row per connection?
Pre-upgrade handshakes#
A WebSocket starts life as an HTTP GET with an Upgrade: websocket header. If that fails with a 4xx or 5xx (auth, CORS, server unavailable) the upgrade never happens — and that request is already captured by the network module as a regular failed HTTP call.
The WebSocket module only takes over after the 101 Switching Protocols response. So a failed connection attempt shows up in Network, while a connection that opened and then dropped shows up in WebSockets.
What gets sent#
| Name | Type | Default | Description |
|---|---|---|---|
| url | string | — | Full WebSocket URL (ws:// or wss://). Sensitive query params like tokens or keys are scrubbed. |
| url_template | string | — | Normalized path — numeric, UUID, and long-hex segments collapse to :id / :uuid / :hex so /chat/users/42 and /chat/users/99 group together. |
| protocols | string[] | — | Sub-protocols negotiated at open (the second argument to new WebSocket(url, protocols)). |
| opened_at | string | — | ISO timestamp when the connection was created. |
| closed_at | string | — | ISO timestamp when the connection closed. |
| duration_ms | number | — | Lifetime of the connection in milliseconds. |
| close_code | number | — | WebSocket close code (1000 normal, 1001 going away, 1006 abnormal, 1011–1014 server error, 4000–4999 app-defined). |
| close_reason | string | — | Optional reason string the peer included with the close frame. |
| had_error | boolean | — | Server-computed verdict: true if any error event fired during the lifetime OR the close code is anything other than 1000/1001. |
| error_count | number | — | How many error events fired during the connection's lifetime. |
| messages_sent | number | — | Total messages your code sent over the connection. |
| messages_received | number | — | Total messages received from the peer. |
| bytes_sent | number | — | Sum of outbound payload sizes (strings are measured as UTF-8 bytes; Blob/ArrayBuffer use their byte length). |
| bytes_received | number | — | Sum of inbound payload sizes. |
| reconnect_count | number | — | Number of prior opens to the same url_template within a 30-second sliding window. A value > 3 signals broken backoff. |
| path | string | — | The page route the user was on when the connection opened. |
Errors flow into the errors pipeline#
A WebSocket summary on its own doesn't page anyone — but the failures inside it do. Three classes of events are forwarded to the standard errors pipeline so they participate in incident grouping, alerts, and paging:
- Post-open
errorevents — anything the WebSocket spec surfaces via theerrorhandler. - Abnormal closes — close codes outside
1000(normal) and1001(navigation). This includes1006(the socket died without a close frame, usually a network drop) and1011–1014(server-side errors). send()on a closed socket — a programming bug worth surfacing, even if the underlying call already throws.
Each forwarded error is tagged with source: "websocket", the URL, and the connection UUID — so you can filter the Errors view to just WebSocket failures, or pivot from a WebSocket summary row into the matching error in one click.
Reconnect storm detection#
When a new connection opens to a URL template that's already opened recently, the SDK stamps the running count onto the new row. Backoff is healthy when this stays at 0–2; values above 3 within the 30-second window mean the reconnect loop is hot and the dashboard will start flagging them.
// Healthy lifecycle — single open, clean close
{ reconnect_count: 0, close_code: 1000, duration_ms: 480_000 }
// Suspect — repeated opens to the same endpoint inside 30s
{ reconnect_count: 5, close_code: 1006, duration_ms: 1_200 }Toggling it#
WebSocket capture is enabled by default. Disable it via the captureWebSockets flag if you don't use WebSockets or want to gate it explicitly:
init({
publicKey: 'pk_live_rl_...',
captureWebSockets: false, // default: true
});What's NOT captured#
- Per-message payloads — only counts and total byte sizes. We never store frame contents.
- Sockets opened by browser extensions or other libraries that bypass
window.WebSocket— the wrapper applies atinit(); anything created before that, or that holds a reference to the original constructor, escapes capture. - Server-Sent Events (SSE) and EventSource — those go through fetch and are covered by the network module instead.
Tip
beforeSend(event) {
if (event.path === '/websocket' && event.url_template?.endsWith('/heartbeat')) {
return null;
}
return event;
}