WebSocket
A WebSocket begins as an HTTP request asking to upgrade, and after the handshake it is no longer HTTP: it is a two-way frame channel over one connection, with no methods, no status codes and no per-message headers. Connect uses it in exactly one place — SIP signalling for the browser softphone — and deliberately not for its own application data, which streams over server-sent events instead.
What the upgrade throws away#
GET /ws HTTP/1.1
Host: example-registrar.net
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Version: 13
Sec-WebSocket-Protocol: sip
Origin: https://connectbyjbrh.com
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Sec-WebSocket-Protocol: sipAfter that 101, everything an HTTP-shaped system relies on is gone. There is no per-message path to authorise, no status code to signal a refusal, no cache semantics and no idempotent method. Whatever authorisation you need has to be decided at the handshake or invented inside your own frame format — and inventing it is where the trouble starts, because it is a second permission system that has to be kept in step with the first.
The subprotocol negotiation in that exchange matters too. Sec-WebSocket-Protocol: sip is how both ends agree what the frames *mean*; without it a socket is an untyped pipe, and a mismatch presents as a connection that opens and then does nothing.
Three things nobody gets for free#
- Origin enforcement
- A browser sends
Originon the handshake but does not apply the same-origin policy to WebSocket the way it does to fetch. The server must check it, or any page anywhere can open a socket carrying the visitor's cookies. - Liveness
- A TCP connection can be dead for minutes while both ends believe it is fine. Protocol-level ping and pong frames exist; using them, and acting on a missing pong, is application work.
- Reconnection
- There is none. No resume, no last-event marker, no backoff — all of it is yours to write, and writing it badly produces a reconnect storm the moment a server restarts.
Server-sent events gives you the second and third of those in the browser, which is most of the reason Connect's own stream is SSE. The comparison is not about which protocol is more capable; it is about which one leaves less to reimplement for a problem that only runs one way.
Does Connect use WebSocket?#
Used, in exactly one place: the browser softphone's SIP signalling. A person's own line is a channel_routes row with channel='softphone', and the browser registers with the carrier's registrar over SIP carried on a WebSocket. That is not a design preference — carrying SIP into a browser is what the standard specifies, and there is no alternative transport for it.
Not used for application data. Conversations, approvals, call events and everything else the app learns about arrive on the server-sent event stream. A client that opens a socket expecting product data will find nothing to talk to.
When a socket is the right answer#
- The client genuinely originates messages at high rate. A softphone sending SIP is the archetype: signalling flows both ways, continuously, and turning each message into an HTTP request would be absurd.
- Latency per message is the constraint. Frames avoid per-request overhead. This matters in the tens of milliseconds, which is real for media control and imaginary for a notification badge.
- Binary payloads. SSE is text; a socket carries bytes without an encoding step.
- Otherwise, prefer one-way streaming. If the honest description of your need is 'tell me when something changes', a duplex socket buys you reconnection code and a second authorisation surface in exchange for nothing.
Questions#
Can I subscribe to Connect events over a WebSocket?
No. The event surface is server-sent events, described in an AsyncAPI 3.1.0 document. There is no socket endpoint for product data, and the softphone's signalling socket is not a general-purpose one.
Does a WebSocket bypass the tenant allowlist?
It would, which is precisely the argument against using one for product data. Because every state change in Connect arrives as an ordinary HTTP request, it passes the customer_safe allowlist, the workspace kernel and row-level security every time, with no second path to keep in step.
Why does my softphone show registered while calls fail?
Because registration is signalling and calls are media. The socket proving the browser reached the registrar says nothing about whether audio can flow between the endpoints, which depends on the media path and its own set of network conditions.