Enabling compression
Request compression at the handshake in one of two ways:zstd is the only supported value; an unsupported value fails the upgrade with 400. Subscribe-message compression is not used, only the handshake matters.
The header is preferred because it keeps the compression choice out of the URL and logs. Use the query parameter only when your client can’t set upgrade headers, as is the case for the browser
WebSocket constructor. If you set both, they must agree.Decoding frames
A compressed connection delivers each room envelope as a binary frame. Decompress the bytes with zstd, thenJSON.parse the resulting UTF-8 string exactly as you would an uncompressed text frame.
Browsers have no native zstd decoder (
DecompressionStream supports only gzip and deflate), so a browser client needs a small library such as fzstd to decompress frames. The TypeScript SDK handles this for you.{"type":"pong"} keepalive reply still arrive as text, so a typeof data === "string" (or isBinary / instanceof ArrayBuffer) check keeps both paths working on the same connection.
Confirming the negotiated mode
Each room’s*_subscribe_response echoes a compression field reporting whether zstd delivery is active for the connection, so you can assert the negotiation succeeded without inspecting frame types.
When to use it
Compression trades a little CPU on both ends for lower bandwidth. It pays off most on high-volume subscriptions, verbose JSON payloads (metrics and order-book rooms), firehose subscriptions, and bandwidth-constrained or metered clients. For a handful of narrowly filtered rooms the savings are marginal, so leave it off unless you’re moving real volume.Next steps
- Firehose subscriptions — the highest-volume streams, where compression matters most.
- WebSocket Pricing — per-message rates (compression doesn’t change billing; you’re billed per message, not per byte).
- Getting Started — the full Rooms protocol.