INTEGRITY Cloudflare Docs

WebSockets

Background

WebSockets allow you to communicate in real time with your Cloudflare Workers serverless functions. For a complete example, refer to Using the WebSockets API.

Constructor

// { 0: <WebSocket>, 1: <WebSocket> }
let websocketPair = new WebSocketPair();

The WebSocketPair returned from this constructor is an Object, with two WebSockets at keys 0 and 1.

These WebSockets are commonly referred to as client and server. The below example combines Object.values and ES6 destructuring to retrieve the WebSockets as client and server:

let [client, server] = Object.values(new WebSocketPair());

Methods

accept

Parameters

addEventListener

Parameters

close

Parameters

send

Parameters


Properties

readyState

binaryType


Events

Types

Message


Close behavior

With the web_socket_auto_reply_to_close compatibility flag (enabled by default on compatibility dates on or after 2026-04-07), the Workers runtime automatically sends a reciprocal Close frame when it receives a Close frame from the peer. The readyState transitions to CLOSED before the close event fires. This matches the WebSocket specification and standard browser behavior.

If you still call close() inside the close event handler, the call is silently ignored. Existing code that manually replies to Close frames will continue to work without changes.

server.addEventListener("close", (event) => {
  // readyState is already CLOSED — no need to call server.close().
  console.log(server.readyState); // WebSocket.CLOSED
  console.log(event.code);        // 1000
  console.log(event.wasClean);    // true
});

Half-open mode for proxying

The automatic close behavior can interfere with WebSocket proxying, where a Worker sits between a client and a backend and needs to coordinate the close on both sides independently. To support this, pass { allowHalfOpen: true } to accept():

server.accept({ allowHalfOpen: true });

server.addEventListener("close", (event) => {
  // readyState is still CLOSING here, giving you time
  // to coordinate the close on the other side.
  console.log(server.readyState); // WebSocket.CLOSING

  // Manually close when ready.
  server.close(event.code, "done");
});

Prior behavior

On compatibility dates before 2026-04-07 (or with the web_socket_manual_reply_to_close flag), receiving a Close frame leaves the WebSocket in CLOSING state, and your code must call close() to complete the handshake. Failing to do so can result in 1006 abnormal closure errors on the client.


Binary messages

WebSocket frames carry either text or binary payloads, and the choice between the two is made by the sender at the time the frame is sent. Text frames are always delivered to the message event as JavaScript strings. Binary frames are delivered either as Blob or as ArrayBuffer, depending on the WebSocket's binaryType.

With the websocket_standard_binary_type compatibility flag (enabled by default on compatibility dates on or after 2026-03-17), binaryType defaults to "blob" and binary frames are delivered as Blob objects. This matches the WebSocket specification and standard browser behavior. Without the flag, binaryType defaults to "arraybuffer" and binary frames are delivered as ArrayBuffer, matching the runtime's historical behavior.

The binaryType property itself is always available. To opt back into ArrayBuffer delivery for a single WebSocket, assign binaryType before calling accept():

const resp = await fetch("https://example.com", {
  headers: { Upgrade: "websocket" },
});
const ws = resp.webSocket;

// Opt back into ArrayBuffer delivery for this WebSocket.
ws.binaryType = "arraybuffer";
ws.accept();

ws.addEventListener("message", (event) => {
  if (typeof event.data === "string") {
    // Text frame.
  } else {
    // event.data is an ArrayBuffer because we set binaryType above.
  }
});

Reading binary payloads

An incoming binary frame is fully buffered before the message event fires, regardless of binaryType. The choice between Blob and ArrayBuffer does not change when or whether the frame is received — only how you access its bytes:

Under the new default, a binary message handler must be async in order to read the payload. If you want to keep an existing synchronous handler, set binaryType to "arraybuffer" on the WebSocket.

When the value takes effect

Per the WebSocket specification, binaryType is mutable: the value is consulted at the moment each binary frame is dispatched to the message event, so assigning a new value affects only subsequent messages. If you want every binary message on a WebSocket to be delivered as the same type, assign binaryType before calling accept(). That guarantees the setting is in place before the runtime starts dispatching any incoming frames.

Worker-wide opt-out

If you are not ready to migrate and want to keep ArrayBuffer as the default for every WebSocket in your Worker, add the no_websocket_standard_binary_type flag to your Wrangler configuration file. Individual WebSockets can still override the default by assigning binaryType.