You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
When a server sends a WebSocket Close frame, the Workers runtime now automatically sends a reciprocal Close frame and transitions `readyState` to `CLOSED` before firing the `close` event. This matches the [WebSocket spec](https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/close_event) and browser behavior.
15
+
16
+
Previously, receiving a server-initiated Close frame left the WebSocket in `CLOSING` and required the application to call `close()` itself. With this flag active, you no longer need to call `close()` in your `close` event handler. The runtime handles the close handshake automatically.
If you do still call `close()` inside the handler, the call is silently ignored. This means existing code that manually replies to Close frames will not break when you update your compatibility date.
31
+
32
+
The automatic close behavior can interfere with WebSocket proxying. When a Worker proxies between a client and a backend, the old behavior allowed the Worker to observe a backend Close frame without the runtime tearing down the connection, giving the Worker time to coordinate a clean close on the client side. To support this pattern, the `accept()` method now accepts an option `allowHalfOpen`. Call `ws.accept({ allowHalfOpen: true })` to restore the old half-open behavior regardless of the compatibility flag.
Note that there is no corresponding option to the `WebSocket` constructor. WebSockets constructed with `new WebSocket` will always auto-reply to closes after this flag takes effect. WebSockets constructed this way are automatically "accepted", so there is no opportunity to pass the option to `accept()`. If you are creating a WebSocket with `new WebSocket`, but you need half-open behavior, you will need to switch to using `fetch()` instead.
51
+
52
+
```js
53
+
// This does not allow half-open:
54
+
let ws =newWebSocket("wss://example.com");
55
+
56
+
// But you can do this instead:
57
+
let resp =awaitfetch("https://example.com", {
58
+
headers: { "Upgrade":"websocket" }
59
+
});
60
+
if (!resp.webSocket) {
61
+
thrownewError("WebSocket handshake not accepted");
62
+
}
63
+
let ws =resp.webSocket;
64
+
ws.accept({ allowHalfOpen:true });
65
+
```
66
+
67
+
For more information, refer to the [WebSocket API documentation](/workers/runtime-apis/websockets/).
: <Typetext="void" /> | <Typetext="Promise<void>" />- Called by the system
178
-
when a WebSocket connection is closed. - You **must** call `ws.close(code,
179
-
reason)` inside this handler to complete the WebSocket close handshake.
180
-
Failing to reciprocate the close will result in `1006` errors on the client,
181
-
representing an abnormal closure per the WebSocket specification.
178
+
when a WebSocket connection is closed.
179
+
- With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the runtime automatically sends a reciprocal Close frame and transitions `readyState` to `CLOSED` before this handler is called. You do not need to call `ws.close()` — but doing so is safe (the call is silently ignored).
180
+
- On older compatibility dates (before `2026-04-07`), you **must** call `ws.close(code, reason)` inside this handler to complete the WebSocket close handshake. Failing to reciprocate the close will result in `1006` errors on the client, representing an abnormal closure per the WebSocket specification.
182
181
- This method can be `async`.
183
182
184
183
#### Parameters
@@ -198,7 +197,9 @@ export class MyDurableObject extends DurableObject<Env> {
Copy file name to clipboardExpand all lines: src/content/docs/durable-objects/api/state.mdx
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -190,7 +190,9 @@ The WebSocket Hibernation API permits a maximum of 32,768 WebSocket connections
190
190
191
191
:::note[`waitUntil` is not necessary]
192
192
193
-
Disconnected WebSockets are not returned by this method, but `getWebSockets` may still return WebSockets even after `ws.close` has been called. For example, if the server-side WebSocket sends a close, but does not receive one back (and has not detected a disconnect from the client), then the connection is in the CLOSING 'readyState'. The client might send more messages, so the WebSocket is technically not disconnected.
193
+
Disconnected WebSockets are not returned by this method, but `getWebSockets` may still return WebSockets even after `ws.close` has been called. For example, if the server-side WebSocket sends a close, but does not receive one back (and has not detected a disconnect from the client), then the connection is in the `CLOSING` readyState. The client might send more messages, so the WebSocket is technically not disconnected.
194
+
195
+
With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the runtime automatically completes the close handshake, so WebSockets transition from `CLOSING` to `CLOSED` much faster and are less likely to be observed in the `CLOSING` state.
@@ -1204,7 +1205,7 @@ With the Hibernation API, your Durable Object can go to sleep when there is no a
1204
1205
Best practices:
1205
1206
1206
1207
- The [WebSocket Hibernation API](/durable-objects/best-practices/websockets/#durable-objects-hibernation-websocket-api) exposes `webSocketError`, `webSocketMessage`, and `webSocketClose` handlers for their respective WebSocket events.
1207
-
-When implementing `webSocketClose`, you **must** reciprocate the close by calling`ws.close()`to avoid swallowing the WebSocket close frame. Failing to do so results in `1006` errors, representing an abnormal close per the WebSocket specification.
1208
+
-With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the runtime automatically completes the close handshake. Calling`ws.close()`in `webSocketClose` is still safe but no longer required. On older compatibility dates, you **must** call `ws.close()` to avoid `1006` abnormal closure errors.
1208
1209
1209
1210
Refer to [WebSockets](/durable-objects/best-practices/websockets/) for more details.
1210
1211
@@ -1275,7 +1276,8 @@ export class ChatRoom extends DurableObject<Env> {
// Calling close() on the server completes the WebSocket close handshake
154
+
// With web_socket_auto_reply_to_close (compat date >= 2026-04-07), the runtime
155
+
// auto-replies to Close frames. Calling close() is safe but no longer required.
155
156
ws.close(code, reason);
156
157
this.sessions.delete(ws);
157
158
}
@@ -266,7 +267,8 @@ class WebSocketHibernationServer(DurableObject):
266
267
session.ws.send(f"[Durable Object] message: {message}, from: {session_id}, to: all clients except the initiating client. Total connections: {len(self.sessions)}")
Copy file name to clipboardExpand all lines: src/content/docs/workers/examples/websockets.mdx
+10Lines changed: 10 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -329,6 +329,8 @@ async function websocket(url) {
329
329
330
330
// Call accept() to indicate that you'll be handling the socket here
331
331
// in JavaScript, as opposed to returning it on to a client.
332
+
// You can pass { allowHalfOpen: true } if you need to coordinate
333
+
// the close handshake manually (for example, when proxying).
332
334
ws.accept();
333
335
334
336
// Now you can send and receive messages like before.
@@ -339,6 +341,14 @@ async function websocket(url) {
339
341
}
340
342
```
341
343
344
+
## WebSocket close behavior
345
+
346
+
With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the Workers runtime automatically replies to incoming Close frames and transitions `readyState` to `CLOSED` before firing the `close` event. You do not need to call `close()` in your `close` event handler, but doing so is safe (the call is silently ignored).
347
+
348
+
If you need half-open behavior (for example, for WebSocket proxying), pass `{ allowHalfOpen: true }` to `accept()`. Note that `new WebSocket(url)` always auto-replies after this flag takes effect. To get half-open behavior for a client WebSocket, use the `fetch()`-based pattern shown above and call `ws.accept({ allowHalfOpen: true })`.
349
+
350
+
For more details, refer to [WebSocket close behavior](/workers/runtime-apis/websockets/#close-behavior).
351
+
342
352
## WebSocket compression
343
353
344
354
Cloudflare Workers supports WebSocket compression. Refer to [WebSocket Compression](/workers/configuration/compatibility-flags/#websocket-compression) for more information.
Copy file name to clipboardExpand all lines: src/content/docs/workers/observability/errors.mdx
+6Lines changed: 6 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -69,6 +69,12 @@ You can prevent this by enforcing the [`no-floating-promises` eslint rule](https
69
69
70
70
If a WebSocket is missing the proper code to close its server-side connection, the Workers runtime will throw a `script will never generate a response` error. In the example below, the `'close'` event from the client is not properly handled by calling `server.close()`, and the error is thrown. In order to avoid this, ensure that the WebSocket's server-side connection is properly closed via an event listener or other server-side logic.
71
71
72
+
:::note
73
+
74
+
With the [`web_socket_auto_reply_to_close`](/workers/configuration/compatibility-flags/#websocket-auto-reply-to-close) compatibility flag (enabled by default on compatibility dates on or after `2026-04-07`), the runtime automatically completes the WebSocket close handshake. This specific error scenario is less likely to occur because the runtime handles the close for you. The example below applies to Workers on older compatibility dates.
0 commit comments