Documentation Index Fetch the complete documentation index at: https://mintlify.com/cloudflare/workerd/llms.txt
Use this file to discover all available pages before exploring further.
The WebSocket API enables real-time, bidirectional communication between clients and servers. Workerd extends the standard WebSocket API with WebSocketPair for creating connected socket pairs.
Overview
Workerd provides:
Standard WebSocket interface for client connections
WebSocketPair for creating connected socket pairs
CloseEvent for handling connection closure
Hibernation support for Durable Objects
Implementation: src/workerd/api/web-socket.h and web-socket.c++
WebSocketPair
Create a pair of connected WebSockets:
export default {
async fetch ( request ) {
// Create a WebSocket pair
const pair = new WebSocketPair ();
// Accept the server side
pair [ 1 ]. accept ();
// Set up event handlers
pair [ 1 ]. addEventListener ( 'message' , event => {
console . log ( 'Received:' , event . data );
pair [ 1 ]. send ( 'Echo: ' + event . data );
});
// Return the client side to the caller
return new Response ( null , {
status: 101 ,
webSocket: pair [ 0 ]
});
}
} ;
Source: src/workerd/api/tests/websocket-test.js
Accepting WebSocket connections
Handle the WebSocket upgrade:
export default {
async fetch ( request ) {
// Check if it's a WebSocket upgrade request
if ( request . headers . get ( 'Upgrade' ) !== 'websocket' ) {
return new Response ( 'Expected WebSocket' , { status: 400 });
}
const pair = new WebSocketPair ();
const [ client , server ] = pair ;
// Accept the connection
server . accept ();
// Handle messages
server . addEventListener ( 'message' , event => {
server . send ( `You said: ${ event . data } ` );
});
// Handle close
server . addEventListener ( 'close' , event => {
console . log ( 'Connection closed:' , event . code , event . reason );
});
// Return the client side
return new Response ( null , {
status: 101 ,
webSocket: client
});
}
} ;
Sending messages
Text messages
webSocket . send ( 'Hello World' );
Binary messages
// ArrayBuffer
const buffer = new Uint8Array ([ 1 , 2 , 3 , 4 ]). buffer ;
webSocket . send ( buffer );
// Typed array
const bytes = new Uint8Array ([ 1 , 2 , 3 , 4 ]);
webSocket . send ( bytes );
JSON messages
const data = { type: 'greeting' , message: 'Hello' };
webSocket . send ( JSON . stringify ( data ));
Receiving messages
Listen for messages with event listeners:
webSocket . addEventListener ( 'message' , event => {
// event.data contains the message (string or ArrayBuffer)
if ( typeof event . data === 'string' ) {
console . log ( 'Text message:' , event . data );
} else {
console . log ( 'Binary message:' , new Uint8Array ( event . data ));
}
});
Parsing JSON messages
webSocket . addEventListener ( 'message' , event => {
try {
const data = JSON . parse ( event . data );
console . log ( 'Received:' , data );
} catch ( error ) {
console . error ( 'Invalid JSON:' , error );
}
});
Closing connections
Graceful close
// Close with default code (1000)
webSocket . close ();
// Close with custom code and reason
webSocket . close ( 1000 , 'Normal closure' );
Close event
Handle connection closure:
webSocket . addEventListener ( 'close' , event => {
console . log ( 'Closed:' , event . code , event . reason );
console . log ( 'Was clean:' , event . wasClean );
});
Close codes
Common WebSocket close codes:
1000 - Normal closure
1001 - Going away (e.g., server shutdown)
1002 - Protocol error
1003 - Unsupported data
1006 - Abnormal closure (no close frame)
1008 - Policy violation
1009 - Message too big
1011 - Internal server error
Error handling
Handle errors with event listeners:
webSocket . addEventListener ( 'error' , event => {
console . error ( 'WebSocket error:' , event );
});
// Errors automatically close the connection
webSocket . addEventListener ( 'close' , event => {
if ( ! event . wasClean ) {
console . error ( 'Connection closed abnormally' );
}
});
Durable Objects chat example
Complete example from src/workerd/api/samples/durable-objects-chat/chat.js:
export class ChatRoom {
constructor ( state , env ) {
this . state = state ;
this . sessions = [];
}
async fetch ( request ) {
if ( request . headers . get ( 'Upgrade' ) !== 'websocket' ) {
return new Response ( 'Expected WebSocket' , { status: 400 });
}
const pair = new WebSocketPair ();
await this . handleSession ( pair [ 1 ]);
return new Response ( null , {
status: 101 ,
webSocket: pair [ 0 ]
});
}
async handleSession ( webSocket ) {
webSocket . accept ();
// Add to sessions list
const session = { webSocket };
this . sessions . push ( session );
// Handle messages
webSocket . addEventListener ( 'message' , async event => {
const data = JSON . parse ( event . data );
// Broadcast to all sessions
this . broadcast ( JSON . stringify ({
name: session . name ,
message: data . message
}));
// Save to storage
const key = new Date (). toISOString ();
await this . state . storage . put ( key , event . data );
});
// Handle close
webSocket . addEventListener ( 'close' , () => {
this . sessions = this . sessions . filter ( s => s !== session );
this . broadcast ( JSON . stringify ({ quit: session . name }));
});
}
broadcast ( message ) {
for ( const session of this . sessions ) {
try {
session . webSocket . send ( message );
} catch ( error ) {
// Connection already closed
}
}
}
}
Source: src/workerd/api/samples/durable-objects-chat/chat.js:213
Hibernatable WebSockets
Durable Objects can use hibernatable WebSockets to reduce memory usage:
export class HibernatableRoom {
constructor ( state , env ) {
this . state = state ;
this . state . setHibernatableWebSocketEventTimeout ( 30_000 );
}
async webSocketMessage ( ws , message ) {
// Called when a message is received
// The Durable Object may have been hibernating
this . broadcast ( message );
}
async webSocketClose ( ws , code , reason , wasClean ) {
// Called when a connection closes
console . log ( 'Connection closed:' , code , reason );
}
async webSocketError ( ws , error ) {
// Called when an error occurs
console . error ( 'WebSocket error:' , error );
}
}
Implementation: src/workerd/api/hibernatable-web-socket.h
Best practices
The server-side WebSocket must call accept() before sending or receiving messages: const pair = new WebSocketPair ();
pair [ 1 ]. accept (); // Required!
pair [ 1 ]. send ( 'Hello' );
Always add error and close event listeners: webSocket . addEventListener ( 'error' , event => {
console . error ( 'Error:' , event );
});
webSocket . addEventListener ( 'close' , event => {
if ( ! event . wasClean ) {
console . error ( 'Abnormal closure' );
}
});
Always validate and sanitize incoming messages: webSocket . addEventListener ( 'message' , event => {
try {
const data = JSON . parse ( event . data );
// Validate
if ( typeof data . message !== 'string' ) {
throw new Error ( 'Invalid message format' );
}
// Sanitize
const sanitized = data . message . slice ( 0 , 1000 );
// Process
processMessage ( sanitized );
} catch ( error ) {
webSocket . close ( 1003 , 'Invalid message' );
}
});
Protect against message spam: let messageCount = 0 ;
let resetTime = Date . now () + 60000 ;
webSocket . addEventListener ( 'message' , event => {
if ( Date . now () > resetTime ) {
messageCount = 0 ;
resetTime = Date . now () + 60000 ;
}
if ( ++ messageCount > 100 ) {
webSocket . close ( 1008 , 'Rate limit exceeded' );
return ;
}
// Process message
});
CloseEvent
The CloseEvent interface provides information about connection closure:
webSocket . addEventListener ( 'close' , event => {
console . log ( 'Code:' , event . code ); // Close code
console . log ( 'Reason:' , event . reason ); // Close reason string
console . log ( 'Was clean:' , event . wasClean ); // true if clean close
});
Definition: src/workerd/api/web-socket.h:32
Implementation details
The WebSocket API is implemented in:
src/workerd/api/web-socket.h / .c++ - Main WebSocket implementation (1700+ lines)
src/workerd/api/hibernatable-web-socket.h / .c++ - Hibernation support for Durable Objects
WebSocket pairs use the standard socketpair concept where two connected sockets communicate with each other.