---
image: true
author: nathan-flurry
published: "2025-11-24"
category: changelog
keywords: ["websocket","realtime","actions","events","hibernating-websockets","sleep","fault-tolerance"]
title: "Introducing Live WebSocket Migration and Hibernation"
description: "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."
---

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.

## Why WebSockets Have Historically Been Difficult

Traditionally, applications opt to use stateless HTTP requests over WebSockets because:

- **Expensive at scale**: It's expensive to keep WebSockets open for a high number of concurrent users
- **Disruptive upgrades**: Application upgrades interrupt user experience because of WebSocket disconnects
- **Difficult to rebalance load**: You can't rebalance active WebSockets to different machines when receiving a large influx of traffic
- **High blast radius**: Application crashes disconnect all WebSockets

## Introducing Live WebSocket Migration & Hibernation

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:

- **Idle WebSockets require no compute**: Actors can go to sleep while leaving client WebSockets open, meaning you no longer have to pay for active compute resources. Actors automatically wake up when a message is received or the connection closes.
- **Upgrade your application without terminating WebSockets**: Applications can be upgraded without terminating WebSocket connections by automatically migrating the WebSocket connection to the new version of the actor
- **Load rebalancing**: Load is better distributed when scaling up your application since actors can reschedule to machines with less load
- **Resilience**: Application crashes from errors, hardware faults, or network faults no longer interrupt WebSockets. Instead, the actor will immediately reschedule and the WebSocket will continue to operate as if nothing happened

## Show Me the Code

### Actions & Events API

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!");
```
</CodeGroup>

Read more about [actions](/docs/actors/actions) and [events](/docs/actors/events).

### Low-Level WebSocket API

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!" }));
```
</CodeGroup>

Read more about the [low-level WebSocket API](/docs/actors/websocket-handler).
