Sign In
Communication

Fetch and WebSocket Handler

Actors can handle HTTP requests and WebSocket connections through the onFetch and onWebSocket handlers.

For most use cases, actions and events provide high-level connection handling that's easier to work with. However, raw handlers are required when implementing custom use cases or integrating external libraries that need direct access to the underlying HTTP Request/Response objects or WebSocket connections.

Defining Handlers

onFetch(c, request, { auth })

The onFetch handler processes HTTP requests sent to your actor. It receives the actor context and a standard Request object.

WebSocket upgrades are not currently supported in onFetch. Use onWebSocket instead.

TypeScript
import { type ActorContext, actor } from "@rivetkit/core";

export const httpActor = actor({
    state: {
        requestCount: 0,
    },
    actions: {},
    onFetch(ctx, request) {
        const url = new URL(request.url);
        ctx.state.requestCount++;

        if (url.pathname === "/api/hello") {
            return new Response(JSON.stringify({ message: "Hello from actor!" }), {
                headers: { "Content-Type": "application/json" },
            });
        }

        if (url.pathname === "/api/echo" && request.method === "POST") {
            return new Response(request.body, {
                headers: request.headers,
            });
        }

        // Return 404 for unhandled paths
        return new Response("Not Found", { status: 404 });
    },
});

Also see the raw fetch handler example project.

onFetch can be used to expose Server-Sent Events from Rivet Actors.

onWebSocket(c, websocket, { request, auth })

The onWebSocket handler manages WebSocket connections. It receives the actor context, a WebSocket object, and the initial Request.

TypeScript
export const websocketActor = actor({
    state: { messageCount: 0 },
    actions: {},
    onWebSocket(ctx, websocket) {
        websocket.send(JSON.stringify({
            type: "welcome",
            connectionCount: ctx.state.connectionCount,
        }));

        websocket.addEventListener("message", (event) => {
			c.state.messageCount++;

            // Echo messages back
            websocket.send(event.data);
        });
    },
});

Also see the raw WebSocket handler with proxy example project.

Connection lifecycle hooks like onConnect and onDisconnect do not get called when opening WebSockets for onWebSocket. This is because onWebSocket provides a low-level connection. Use ws.addEventListener("open") and ws.addEventListener("close") instead.

Accessing Your Handlers

There are three ways to access your actor's fetch and WebSocket handlers:

Option A: From Backend via RivetKit Client

You can use the RivetKit client's built-in methods for raw HTTP and WebSocket access:

TypeScript
import { createClient } from "@rivetkit/core/client";

const client = createClient("http://localhost:8080");

// HTTP requests using .fetch() method
const actor = client.myActor.getOrCreate(["key"]);
const response = await actor.fetch("/api/hello", {  // Also accepts W3C `Request` object
    method: "GET"
});
const data = await response.json();

// POST request with JSON body
const postResponse = await actor.fetch("/api/echo", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
    },
    body: JSON.stringify({ message: "Hello from client!" }),
});

For more advanced use cases, you can forward requests to actor handlers from your server:

TypeScript
import { Hono } from "hono";
import { registry } from "./registry";

const { client, serve } = registry.createServer();

const app = new Hono();

// Forward requests to actor's fetch handler
app.all("/forward/:name/*", async (c) => {
    const name = c.req.param("name");
    
    // Create new URL with the path truncated
    const truncatedPath = c.req.path.replace(`/forward/${name}`, "");
    const url = new URL(truncatedPath, c.req.url);
    const newRequest = new Request(url, c.req.raw);
    
    // Forward to actor's fetch handler
    const actor = client.counter.getOrCreate(name);
    const response = await actor.fetch(truncatedPath, newRequest);
    
    return response;
});

serve(app);

Option B: From Frontend with RivetKit Client

Use the RivetKit client to make direct HTTP requests or WebSocket connections:

TypeScript
import { createClient } from "@rivetkit/core/client";

const client = createClient("http://localhost:8080");

// HTTP requests
const actor = client.myActor.getOrCreate(["key"]);
const response = await actor.fetch("/api/hello", {  // Also accepts W3C `Request` object
    method: "GET"
});
const data = await response.json();
console.log(data); // { message: "Hello from actor!" }

// POST request with data
const postResponse = await actor.fetch("/api/echo", {
    method: "POST",
    headers: {
        "Content-Type": "application/json",
    },
    body: JSON.stringify({ 
        message: "Hello from frontend!",
        timestamp: Date.now()
    }),
});

// Handle response
if (postResponse.ok) {
    const echoData = await postResponse.json();
    console.log("Echo response:", echoData);
} else {
    console.error("Request failed:", postResponse.status);
}

Option C: From Frontend via Direct RivetKit Router Access

You can access your actor handlers directly through the mounted RivetKit router. The router automatically handles the required headers for authentication and routing.

For HTTP requests, the router expects these headers:

  • X-RivetKit-Actor-Query: JSON-encoded actor query
  • X-RivetKit-Encoding: Encoding type (usually "json")
  • X-RivetKit-Conn-Params: JSON-encoded connection parameters (optional)
TypeScript
// Direct HTTP request to actor
const response = await fetch("http://localhost:8080/registry/actors/myActor/raw/http/api/hello", {
    method: "GET",
    headers: {
        "X-RivetKit-Actor-Query": JSON.stringify({
            getOrCreateForKey: { name: "myActor", key: "default" }
        }),
        "X-RivetKit-Encoding": "json",
        "X-RivetKit-Conn-Params": JSON.stringify({ apiKey: "your-api-key" })
    }
});

const data = await response.json();
console.log(data); // { message: "Hello from actor!" }

// POST request with data
const postResponse = await fetch("http://localhost:8080/registry/actors/myActor/raw/http/api/echo", {
    method: "POST",
    headers: {
        "X-RivetKit-Actor-Query": JSON.stringify({
            getOrCreateForKey: { name: "myActor", key: "default" }
        }),
        "X-RivetKit-Encoding": "json",
        "X-RivetKit-Conn-Params": JSON.stringify({ apiKey: "your-api-key" }),
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        message: "Hello from direct access!",
        timestamp: Date.now()
    })
});

// Handle response
if (postResponse.ok) {
    const echoData = await postResponse.json();
    console.log("Echo response:", echoData);
} else {
    console.error("Request failed:", postResponse.status);
}

Authentication

If you are using the external client, authentication is handled through the onAuth handler. The onAuth handler is executed on the server before the request is sent to the actor, reducing resource load on the actor by filtering out unauthorized requests early.

If you are using the server-side client, then authentication is skipped by default.

See the authentication documentation for detailed information on implementing authentication patterns.

State Saves

State changes in onFetch and onWebSocket handlers are automatically saved after the handler finishes executing.

For onWebSocket handlers specifically, you'll need to manually save state using c.saveState() while the WebSocket connection is open if you want state changes to be persisted immediately. This is because WebSocket connections can remain open for extended periods, and state changes made during event handlers (like message events) won't be automatically saved until the connection closes.

TypeScript
export const websocketActor = actor({
    state: { messageCount: 0, lastMessage: "" },
    actions: {},
    onWebSocket(ctx, websocket) {
        websocket.addEventListener("message", (event) => {
            // Update state
            ctx.state.messageCount++;
            ctx.state.lastMessage = event.data;
            
            // Manually save state to persist changes immediately
            ctx.saveState();
            
            // Echo messages back
            websocket.send(JSON.stringify({
                messageCount: ctx.state.messageCount,
                echo: event.data
            }));
        });
    },
});

For more details on state management, see State.

W3C Compliance

It's not possible to use the global fetch method or global WebSocket class to connect to an actor. This is because actors do not have traditional network interfaces to communicate with.

However, the Request, Response, and WebSocket types used with .fetch() and .websocket() comply with the W3C specification and will work wherever you pass them.

Suggest changes to this page