# Low-Level WebSocket Handler > Source: `src/content/docs/actors/websocket-handler.mdx` > Canonical URL: https://rivet.dev/docs/actors/websocket-handler > Description: Actors can handle WebSocket connections through the `onWebSocket` handler. --- For most use cases, [actions](/docs/actors/actions) and [events](/docs/actors/events) provide high-level connection handling powered by WebSockets that's easier to work with than low-level WebSockets. However, low-level handlers are required when implementing custom use cases. ## Handling WebSocket Connections The `onWebSocket` handler manages low-level WebSocket connections. It receives the actor context and a `WebSocket` object. ```typescript import { actor } from "rivetkit"; export const chatActor = actor({ state: { messages: [] as string[] }, 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) => { // Store message c.state.messages.push(event.data as string); // Echo message back websocket.send(event.data as string); // Manually save state since WebSocket connections are long-lived c.saveState({ immediate: true }); }); }, actions: {} }); ``` See also the [raw WebSocket handler example](https://github.com/rivet-dev/rivet/tree/main/examples/raw-websocket-handler). ## Connecting To Actors ### Via RivetKit Client Use the `.webSocket()` method on an actor handle to open a WebSocket connection to the actor's `onWebSocket` handler. This can be executed from either your frontend or backend. ```typescript index.ts @hide @nocheck import { actor, setup } from "rivetkit"; export const chat = actor({ state: { messages: [] as string[] }, onWebSocket: (c, websocket) => { websocket.addEventListener("message", (event) => { c.state.messages.push(event.data as string); }); }, actions: {} }); export const registry = setup({ use: { chat } }); registry.start(); ``` ```typescript client.ts @nocheck import { createClient } from "rivetkit/client"; import type { registry } from "./index"; const client = createClient("http://localhost:6420"); const actor = client.chat.getOrCreate(["my-chat"]); // Open WebSocket connection const ws = await actor.webSocket("/"); // Listen for messages ws.addEventListener("message", (event) => { const message = JSON.parse(event.data as string); console.log("Received:", message); }); // Send messages ws.send(JSON.stringify({ type: "chat", text: "Hello!" })); ``` The `.webSocket()` method returns a standard WebSocket. ### Via getGatewayUrl Use `.getGatewayUrl()` to get the raw gateway URL for the actor. This is useful when you need to use the URL with external tools or custom WebSocket clients. ```typescript index.ts @hide @nocheck import { actor, setup } from "rivetkit"; export const chat = actor({ state: { messages: [] as string[] }, onWebSocket: (c, websocket) => { websocket.addEventListener("message", (event) => { c.state.messages.push(event.data as string); }); }, actions: {} }); export const registry = setup({ use: { chat } }); registry.start(); ``` ```typescript client.ts @nocheck import { createClient } from "rivetkit/client"; import type { registry } from "./index"; const client = createClient("http://localhost:6420"); const actor = client.chat.getOrCreate(["my-chat"]); // Get the raw gateway URL const gatewayUrl = await actor.getGatewayUrl(); // gatewayUrl = "https://...rivet.dev/..." // Convert to WebSocket URL and connect const wsUrl = gatewayUrl.replace("http://", "ws://").replace("https://", "wss://"); const ws = new WebSocket(`${wsUrl}/websocket/`); ws.addEventListener("message", (event) => { const message = JSON.parse(event.data as string); console.log("Received:", message); }); ws.addEventListener("open", () => { ws.send(JSON.stringify({ type: "chat", text: "Hello!" })); }); ``` ### Via HTTP API This handler can be accessed with raw WebSockets using `wss://api.rivet.dev/gateway/{actorId}@{token}/websocket/{...path}`. For example, to connect to the chat actor above: ```typescript // Replace with your actor ID and token const actorId = "your-actor-id"; const token = "your-token"; const ws = new WebSocket( `wss://api.rivet.dev/gateway/${actorId}@${token}/websocket/` ); ws.addEventListener("message", (event) => { const message = JSON.parse(event.data as string); console.log("Received:", message); }); ws.addEventListener("open", () => { ws.send(JSON.stringify({ type: "chat", text: "Hello!" })); }); ``` ```bash wscat -c "wss://api.rivet.dev/gateway/{actorId}@{token}/websocket/" ``` The path after `/websocket/` is passed to your `onWebSocket` handler and can be used to route to different functionality within your actor. For example, to connect with a custom path `/admin`: ```typescript // Replace with your actor ID and token const actorId = "your-actor-id"; const token = "your-token"; const ws = new WebSocket( `wss://api.rivet.dev/gateway/${actorId}@${token}/websocket/admin` ); ``` ```bash wscat -c "wss://api.rivet.dev/gateway/{actorId}@{token}/websocket/admin" ``` See the [HTTP API reference](/docs/actors/http-api) for more information on WebSocket routing and authentication. ### Via Proxying Connections You can proxy WebSocket connections from your own server to actor handlers using the RivetKit client. This is useful when you need to add custom authentication or connection management before forwarding to actors. ```typescript import { Hono } from "hono"; import type { WSContext, WSMessageReceive } from "hono/ws"; import { upgradeWebSocket } from "hono/bun"; import { createClient } from "rivetkit/client"; import { actor, setup } from "rivetkit"; const chatActor = actor({ state: { messages: [] as string[] }, onWebSocket: (c, websocket) => { websocket.addEventListener("message", (event) => { c.state.messages.push(event.data as string); websocket.send(event.data as string); }); }, actions: {} }); const registry = setup({ use: { chat: chatActor } }); const client = createClient("http://localhost:6420"); const app = new Hono(); // Proxy WebSocket connections to actor's onWebSocket handler app.get("/ws/:id", upgradeWebSocket(async (c) => { const actorId = c.req.param("id"); const actorHandle = client.chat.get([actorId]); const actorWs = await actorHandle.webSocket("/"); return { onOpen: (evt: Event, ws: WSContext) => { actorWs.addEventListener("message", (event: MessageEvent) => { ws.send(event.data); }); actorWs.addEventListener("close", () => { ws.close(); }); }, onMessage: (evt: MessageEvent, ws: WSContext) => { actorWs.send(evt.data as string); }, onClose: () => { actorWs.close(); }, }; })); export default app; ``` See also the [raw WebSocket handler with proxy example](https://github.com/rivet-dev/rivet/tree/main/examples/raw-websocket-handler-proxy). ## Connection & Lifecycle Hooks `onWebSocket` will trigger the `onBeforeConnect`, `onConnect`, and `onDisconnect` hooks. Read more about [lifecycle hooks](/docs/actors/lifecycle). Open WebSockets will be listed in `c.conns`. `conn.send` and `c.broadcast` have no effect on low-level WebSocket connections. Read more about [connections](/docs/actors/connections). ## WinterTC Compliance The `onWebSocket` handler uses standard WebSocket APIs and will work with existing libraries expecting WinterTC-compliant WebSocket objects. ## Advanced ## WebSocket Hibernation WebSocket hibernation allows actors to go to sleep while keeping WebSocket connections alive. Actors automatically wake up when a message is received or the connection closes. Enable hibernation by setting `canHibernateWebSocket: true`. You can also pass a function `(request) => boolean` for conditional control. ```typescript import { actor } from "rivetkit"; export const myActor = actor({ state: {}, options: { canHibernateWebSocket: true, }, actions: {} }); ``` Since `open` only fires once when the client first connects, use `c.conn.state` to store per-connection data that persists across sleep cycles. See [connections](/docs/actors/connections) for more details. ### Accessing the Request The underlying HTTP request is available via `c.request`. This is useful for accessing the path or query parameters. ```typescript import { actor } from "rivetkit"; const myActor = actor({ state: {}, onWebSocket: (c, websocket) => { if (c.request) { const url = new URL(c.request.url); console.log(url.pathname); // e.g., "/admin" console.log(url.searchParams.get("foo")); // e.g., "bar" } }, actions: {} }); ``` ### Skip Ready Wait Connections are normally held at the gateway until the actor is ready. Pass `skipReadyWait: true` on `handle.webSocket()` to connect immediately, including while the actor is still starting or in the [sleep grace period](/docs/actors/lifecycle#shutdown-sequence). See [Skip Ready Wait](/docs/clients/javascript#skip-ready-wait) for details. ### Async Handlers The `onWebSocket` handler can be async, allowing you to perform async code before setting up event listeners: ```typescript import { actor } from "rivetkit"; const myActor = actor({ state: {}, onWebSocket: async (c, websocket) => { // Perform async operations before the connection is ready const metadata = await fetch("https://api.example.com/metadata").then(r => r.json()); websocket.addEventListener("open", () => { // Send metadata on connection websocket.send(JSON.stringify({ metadata })); }); websocket.addEventListener("message", (event) => { // Handle messages }); }, actions: {} }); ``` ## API Reference - [`WebSocketContext`](/typedoc/interfaces/rivetkit.mod.WebSocketContext.html) - Context for WebSocket handlers - [`UniversalWebSocket`](/typedoc/interfaces/rivetkit.mod.UniversalWebSocket.html) - Universal WebSocket interface - [`handleRawWebSocketHandler`](/typedoc/functions/rivetkit.mod.handleRawWebSocketHandler.html) - Function to handle raw WebSocket - [`UpgradeWebSocketArgs`](/typedoc/interfaces/rivetkit.mod.UpgradeWebSocketArgs.html) - Arguments for WebSocket upgrade _Source doc path: /docs/actors/websocket-handler_