website/src/content/posts/2025-11-24-introducing-live-websocket-migration-hibernation/page.mdx
Rivet now supports keeping WebSocket connections alive while actors upgrade, migrate, crash, or sleep. This eliminates many of the biggest pain points of building realtime applications with WebSockets.
This is fully W3C compliant and requires no custom API changes to integrate.
Traditionally, applications opt to use stateless HTTP requests over WebSockets because:
We set out to solve this by enabling WebSockets to Rivet Actors to stay open while the actor upgrades, migrates, crashes, or goes to sleep.
This comes with a series of benefits that previously were only possible using stateless HTTP requests:
If using the actions & events API, upgrade to Rivet v2.0.24 and it will work out of the box.
The following code will automatically use WebSocket hibernation. When users are sitting idle connected to the chat room, the actor can go to sleep while still keeping the WebSocket open, ready to send actions or receive events:
<CodeGroup> ```typescript Actor import { actor } from "rivetkit";interface Message { id: string; username: string; text: string; timestamp: number; }
interface State { messages: Message[]; }
const chatRoom = actor({ state: { messages: [] } as State,
actions: { setUsername: (c, username: string) => { c.conn.state.username = username; },
sendMessage: (c, text: string) => {
const message = {
id: crypto.randomUUID(),
username: c.conn.state.username,
text,
timestamp: Date.now()
};
c.state.messages.push(message);
// Broadcast to all connected clients
c.broadcast("messageReceived", message);
return message;
},
getHistory: (c) => {
return c.state.messages;
}
} });
```typescript Client
import { createClient } from "rivetkit/client";
import type { registry } from "./registry";
const client = createClient<typeof registry>("http://localhost:8080");
// Connect to the chat room
const chatRoom = client.chatRoom.getOrCreate("general");
const connection = chatRoom.connect();
// Listen for new messages
connection.on("messageReceived", (message) => {
console.log(`${message.username}: ${message.text}`);
});
// Set username (stored in per-connection state)
await connection.setUsername("alice");
// Send a message
await connection.sendMessage("Hello everyone!");
Read more about actions and events.
The low-level WebSocket API (onWebSocket) can opt in to WebSocket hibernation starting in Rivet v2.0.24. Configure options.canHibernateWebSocket with either true or a conditional closure based on the request ((request) => boolean).
The open, message, and close events fire as they normally would on a WebSocket. When the actor migrates to a separate machine, c.conn.state is persisted and no new open event is triggered.
For example:
<CodeGroup> ```typescript Actor import { actor } from "rivetkit";interface State { messages: string[]; }
const chatRoom = actor({ state: { messages: [] } as State,
options: { canHibernateWebSocket: true },
onWebSocket: (c, websocket) => { websocket.addEventListener("open", () => { // Send existing messages to new connection websocket.send(JSON.stringify({ type: "history", messages: c.state.messages })); });
websocket.addEventListener("message", (event) => {
const data = JSON.parse(event.data);
if (data.type === "setUsername") {
// Store username in per-connection state (persists across sleep cycles)
c.conn.state.username = data.username;
return;
}
if (data.type === "message") {
// Store and broadcast the message with username from connection state
const message = `${c.conn.state.username}: ${data.text}`;
c.state.messages.push(message);
websocket.send(message);
c.saveState();
}
});
} });
```typescript Client
import { createClient } from "rivetkit/client";
import type { registry } from "./registry";
const client = createClient<typeof registry>("http://localhost:8080");
// Get the chat room actor
const chatRoom = client.chatRoom.getOrCreate("general");
// Open a WebSocket connection
const ws = await chatRoom.websocket("/");
// Listen for messages
ws.addEventListener("message", (event) => {
console.log("Received:", event.data);
});
// Set username (stored in per-connection state)
ws.send(JSON.stringify({ type: "setUsername", username: "alice" }));
// Send a message
ws.send(JSON.stringify({ type: "message", text: "Hello from WebSocket!" }));
Read more about the low-level WebSocket API.