1. Topics
  2. /
  3. Protocols
  4. /
  5. How do WebSockets work?
12 min readPublished Apr 25, 2023

How do WebSockets work?

Alex Diaconu
Written by:
Alex Diaconu
Copy link to clipboard

What is WebSocket?

In a nutshell, WebSocket is a realtime web technology that enables bidirectional, full-duplex communication between client and server over a persistent connection. The WebSocket connection is kept alive for as long as needed (in theory, it can last forever), allowing the server and the client to send data at will, with minimal overhead.

Further reading:

Copy link to clipboard

How WebSockets work: An overview

At a high level, working with WebSockets involves three main steps:

  • Opening a WebSocket connection. The process of establishing a WebSocket connection is known as the opening handshake, and consists of an HTTP request/response exchange between the client and the server. See How to establish a WebSocket connection for more details.  

  • Data transmission over WebSockets. After a successful opening handshake, the client and server can exchange messages (frames) over the persistent WebSocket connection. WebSocket messages may contain string (plain text) or binary data. Learn more about data transmission over WebSockets

  • Closing a WebSocket connection. Once the persistent WebSocket connection has served its purposes, it can be terminated; both the client and the server can initiate the closing handshake by sending a close message. Read more about closing a WebSocket connection.    

The rest of the article explores these steps in more detail. For each step, we will look at things firstly from a protocol perspective (as described in RFC 6455). Then, we will see how you as a developer can open/close, and send data using the WebSocket API in browsers. 

Copy link to clipboard

How to establish a WebSocket connection

Copy link to clipboard

Establishing a connection at the WebSocket protocol level

Per the WebSocket protocol specification, the process of establishing a WebSocket connection is known as the opening handshake, and consists of an HTTP/1.1 request/response exchange between the client and the server. The client always initiates the handshake; it sends a GET request to the server, indicating that it wants to upgrade the connection from the HTTP protocol to WebSocket. 

Here’s a basic example of a GET request made by the client to initiate the opening handshake:

GET wss://example.com:8181/ HTTP/1.1
Host: localhost: 8181
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: zy6Dy9mSAIM7GJZNf9rI1A==

The server must return an HTTP 101 Switching Protocols response code for the WebSocket connection to be successfully established:

HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Sec-WebSocket-Accept: EDJa7WCAQQzMCYNJM42Syuo9SqQ=
Upgrade: websocket
Copy link to clipboard

Opening handshake headers

The table below describes the headers used by the client and the server during the opening handshake - both the required ones (illustrated in the code snippets above) and the optional ones.






The host name and optionally the port number of the server to which the request is being sent. 



Indicates that the client wants to negotiate a change in the way the connection is being used. Value must be Upgrade.

Also returned by the server.



The only accepted value is 13. Any other version passed in this header is invalid.



A base64-encoded one-time random value (nonce) sent by the client. Automatically handled for you by most WebSocket libraries or by using the WebSocket class provided in browsers. 



A base64-encoded SHA-1 hashed value returned by the server as a direct response to Sec-WebSocket-Key

Indicates that the server is willing to initiate the WebSocket connection. 



Optional header field, containing a list of values indicating which subprotocols the client wants to speak, ordered by preference. 

The server needs to include this field together with one of the selected subprotocol values (the first one it supports from the list) in the response.



Optional header field, initially sent from the client to the server, and then subsequently sent from the server to the client. 

It helps the client and server agree on a set of protocol-level extensions to use for the duration of the connection.



Header field sent by all browser clients (optional for non-browser clients). 

Used to protect against unauthorized cross-origin use of a WebSocket server by scripts using the WebSocket API in a web browser.

The connection will be rejected if the Origin indicated is unacceptable to the server.

Copy link to clipboard

Sec-WebSocket-Key and Sec-WebSocket-Accept

It’s worth mentioning a few more details about two of the required headers used during the WebSocket handshake: Sec-WebSocket-Key and Sec-WebSocket-Accept. Together, these headers are essential in guaranteeing that both the server and the client are capable of communicating over WebSockets. 

First, we have Sec-WebSocket-Key, which is passed by the client to the server, and contains a 16-byte, base64-encoded one-time random value (nonce). Its purpose is to help ensure that the server does not accept connections from non-WebSocket clients (e.g., HTTP clients) that are being abused (or misconfigured) to send data to unsuspecting WebSocket servers. Here’s an example of Sec-WebSocket-Key:

Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==

In direct relation to Sec-WebSocket-Key, the server response includes a Sec-WebSocket-Accept header. This header contains a base64-encoded SHA-1 hashed value generated by concatenating the Sec-WebSocket-Key nonce sent by the client, and the static value (UUID) 258EAFA5-E914-47DA-95CA-C5AB0DC85B11.

Based on the Sec-WebSocket-Key example provided above, here’s the Sec-WebSocket-Accept header returned by the server:

Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Copy link to clipboard

Establishing a connection at the WebSocket API level

The WebSocket API in browsers (and most WebSocket libraries) automatically handles the opening handshake for you. All you have to do is to instantiate the WebSocket object, which will automatically attempt to open the connection to the server:

const socket = new WebSocket('wss://example.org');

An open event is raised when a WebSocket connection is established. It indicates that the opening handshake between the client and the server was successful, and the WebSocket connection can now be used to send and receive data. Here’s an example (note that the open event is handled through the onopen property):

// Create WebSocket connection
const socket = new WebSocket('wss://example.org');

// Connection opened
socket.onopen = function(e) {
   console.log('Connection open!');
Copy link to clipboard

How to transmit data over WebSockets

Copy link to clipboard

Data transmission: Protocol-level considerations

After a successful opening handshake, the client and the server can use the WebSocket connection to exchange messages in full-duplex mode. A WebSocket message consists of one or more frames.

The WebSocket frame has a binary syntax and contains several pieces of information, as shown in the following figure:

Let’s quickly summarize them:

  • FIN bit - indicates whether the frame is the final fragment in a WebSocket message.

  • RSV 1, 2, 3 - reserved for WebSocket extensions.

  • Opcode - determines how to interpret the payload data.

  • Mask - indicates whether the payload is masked or not.

  • Masking key - key used to unmask the payload data.

  • (Extended) payload length - the length of the payload.

  • Payload data - consists of application and extension data.\

We will now take a more detailed look at all these constituent parts of a WebSocket frame.  

Copy link to clipboard

FIN bit and fragmentation

There are numerous scenarios where fragmenting a WebSocket message into multiple frames is required (or at least desirable). For example, fragmentation is often used to improve performance. Without fragmentation, an endpoint would have to buffer the entire message before sending it. With fragmentation, the endpoint can choose a reasonably sized buffer, and when that is full, send subsequent frames as a continuation. The receiving endpoint then assembles the frames to recreate the WebSocket message.    

Here’s what a single-frame message might look like:

0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f (contains "Hello")

In comparison, with fragmentation, the same message would look like this:

0x01 0x03 0x48 0x65 0x6c (contains "Hel")
0x80 0x02 0x6c 0x6f (contains "lo")

The WebSocket protocol makes fragmentation possible via the first bit of the WebSocket frame — the FIN bit, which indicates whether the frame is the final fragment in a message. If it is, the FIN bit must be set to 1. Any other frame must have the FIN bit clear. 

RSV1, RSV2, and RSV3 are reserved bits. They must be 0 unless an extension was negotiated during the opening handshake that defines non-zero values.

Every frame has an opcode that determines how to interpret that frame’s payload data. The standard opcodes currently in use are defined by RFC 6455 and maintained by the Internet Assigned Numbers Authority (IANA)




Continuation frame; continues the payload from the previous frame.


Indicates a text frame (UTF-8 text data).


Indicates a binary frame.


Reserved for custom data frames.


Connection close frame; leads to the connection being terminated.


A ping frame. Serves as a heartbeat mechanism ensuring the connection is still alive. The receiver must respond with a pong frame. 


A pong frame. Serves as a heartbeat mechanism ensuring the connection is still alive. Sent as a response after receiving a ping frame.


Reserved for custom control frames.

Each WebSocket frame sent by the client to the server needs to be masked with the help of a random masking-key (32-bit value). This key is contained within the frame, and it’s used to obfuscate the payload data. However, when data flows the other way around, the server must not mask any frames it sends to the client. 

On the server-side, frames received from the client must be unmasked before further processing. Here’s an example of how you can do that:

var unmask = function(mask, buffer) {
   var payload = new Buffer(buffer.length);
   for (var i=0; i<buffer.length; i++) {
       payload[i] = mask[i % 4] ^ buffer[i];
   return payload;
Copy link to clipboard

Payload length and payload data

The WebSocket protocol encodes the length of the payload data using a variable number of bytes:

  • For payloads <126 bytes, the length is packed into the first two frame header bytes. 

  • For payloads of 126 bytes, two extra header bytes are used to indicate length. 

  • If the payload is 127 bytes, eight additional header bytes are used to indicate its length. 

The WebSocket protocol supports two types of payload data: text (UTF-8 Unicode text) and binary

Copy link to clipboard

Data transmission with the WebSocket API

WebSocket programming follows an asynchronous, event-driven programming model. As long as a WebSocket connection is open, the client and the server simply listen for events in order to handle incoming data and changes in connection status (with no need for polling).

The message event is fired when data is received through a WebSocket. Messages might contain string (plain text) or binary data, and it's up to you how that data will be processed and visualized. 

Here’s an example of how to handle a message event (using the onmessage property):

socket.onmessage = function(msg) {
   if(msg.data instanceof ArrayBuffer) {
   } else {

To send messages via the WebSocket API you have to use the send() method, as demonstrated below:

socket.onopen = function(e) {
   socket.send(JSON.stringify({'msg': 'payload'}));

The sample code above shows how to send text (string) messages. However, in addition to strings, you can also send binary data (Blob or ArrayBuffer):

var buffer = new ArrayBuffer(128);

var intview = new Uint32Array(buffer);

var blob = new Blob([buffer]);
Copy link to clipboard

How to close WebSocket connections

Copy link to clipboard

Closing a WebSocket connection at the protocol level

The process of closing a WebSocket connection is known as the closing handshake. You initiate it by sending a close frame with an opcode of 8. In addition to the opcode, the close frame may contain a body that indicates the reason for closing. This body consists of a status code (integer) and a UTF-8 encoded string (the reason).

The standard status codes that can be used during the closing handshake are defined by RFC 6455, and listed in the following table:

Status code





Codes below 1000 are invalid and cannot be used.


Normal closure

Indicates a normal closure, meaning that the purpose for which the WebSocket connection was established has been fulfilled.


Going away

Should be used when closing the connection and there is no expectation that a follow-up connection will be attempted (e.g., server shutting down, or browser navigating away from the page). 


Protocol error

The endpoint is terminating the connection due to a protocol error. 


Unsupported data

The connection is being terminated because the endpoint received data of a type it cannot handle (e.g., a text-only endpoint receiving binary data).



Reserved. A meaning might be defined in the future. 


No status received

Used by apps and the WebSocket API to indicate that no status code was received, although one was expected.


Abnormal closure

Used by apps and the WebSocket API to indicate that a connection was closed abnormally (e.g., without sending or receiving a close frame). 


Invalid payload data

The endpoint is terminating the connection because it received a message containing inconsistent data (e.g., non-UTF-8 data within a text message).


Policy violation

The endpoint is terminating the connection because it received a message that violates its policy. This is a generic status code; it should be used when other status codes are not suitable, or if there is a need to hide specific details about the policy.


Message too big

The endpoint is terminating the connection due to receiving a data frame that is too large to process.


Mandatory extension

The client is terminating the connection because the server failed to negotiate an extension during the opening handshake.


Internal error

The server is terminating the connection because it encountered an unexpected condition that prevented it from fulfilling the request.


Service restart

The server is terminating the connection because it is restarting. 


Try again later

The server is terminating the connection due to a temporary condition, e.g., it is overloaded. 


Bad gateway

The server was acting as a gateway or proxy and received an invalid response from the upstream server. Similar to 502 Bad Gateway HTTP status code.


TLS handshake

Reserved. Indicates that the connection was closed due to a failure to perform a TLS handshake (e.g., the server certificate can’t be verified). 



Reserved for future use by the WebSocket standard.



Reserved for future use by WebSocket extensions.



Reserved for use by libraries, frameworks, and applications. Available for registration at IANA on a first-come, first-serve basis.



Range reserved for private use in applications. 

Both the client and the web server can initiate the closing handshake. Upon receiving a close frame, an endpoint (client or server) has to send a close frame as a response (echoing the status code received). After an endpoint has both sent and received a close frame, the closing handshake is complete, and the WebSocket connection is considered closed.

Copy link to clipboard

Closing a WebSocket connection with the WebSocket API 

The close() method is used to close the WebSocket connection. After this method is called, no more data can be sent or received over the WebSocket connection. 

Here’s the most basic example of calling the close() method:


Optionally, you can pass two arguments with the close() method:

  • code. A numeric value indicating the status code explaining why the connection is being closed. See the Status codes table in the previous section of this article for details.

  • reason.  A human-readable string explaining why the connection is closing. 

Here’s an example of calling the close() method with the two optional parameters:

socket.close(1003, "Unsupported data type!");

A close event fires when the WebSocket connection closes. This is how you listen for a close event:

socket.onclose = function(e) {
   console.log("Connection closed", e);
Copy link to clipboard

The challenges of using WebSockets

We hope this article gives you a good understanding of how to open a WebSocket connection, send data over it, and then close the connection once it has fulfilled its purpose. As we have seen, the WebSocket API is intuitive and designed with simplicity in mind. As a developer, this allows you to get started quickly with WebSockets.

However, things can be tricky when you use WebSockets in a production environment, especially at scale. WebSockets are fundamentally hard to scale because they’re stateful, and connections to your WebSocket server need to be persistent. The situation becomes complicated when you scale horizontally, as you need a solution for sharing connection state across your WebSocket server farm. Plus, building a scalable, production-ready system with WebSockets means you’ll have to ponder things like:

  • What degree of client interoperability do I need?

  • Do I need guarantees on message delivery, and if so, what strategies can I implement to this end?

  • How many connections are active on my server?

  • Are any connections hogging all of my server’s resources?

  • Are any connections idle and should ideally be dropped?

  • How much bandwidth is being used overall, and how is it impacting my budget?

  • Do I have to deal with traffic spikes, and if so, what is the performance impact on the server layer?

  • How will I automatically add additional server capacity if and when it’s needed?

Learn more about:

Copy link to clipboard

Ably, the WebSocket platform that works reliably at any scale

Ably is a realtime experience infrastructure provider. Our APIs and SDKs help developers build and deliver realtime experiences without having to worry about maintaining and scaling messy WebSocket infrastructure. 

Key Ably features and capabilities:

  • Pub/sub messaging over serverless WebSockets, with rich features such as message delta compression, automatic reconnections with continuity, user presence, message history, and message interactions.

  • A globally-distributed network of datacenters and edge acceleration points-of-presence. 

  • Guaranteed message ordering and delivery. 

  • Global fault tolerance and a 99.999% uptime SLA.

  • < 65ms round-trip latency (P99).

  • Dynamic elasticity, so we can quickly scale to handle any demand (billions of WebSocket messages sent to millions of pub/sub channels and WebSocket connections). 

Join the Ably newsletter today

1000s of industry pioneers trust Ably for monthly insights on the realtime data economy.
Enter your email