# Low-Level HTTP Request Handler > Source: `src/content/docs/actors/request-handler.mdx` > Canonical URL: https://rivet.dev/docs/actors/request-handler > Description: Actors can handle HTTP requests through the `onRequest` handler. --- For most use cases, [actions](/docs/actors/actions) provide high-level API powered by HTTP that's easier to work with than low-level HTTP. However, low-level 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. ## Handling HTTP Requests The `onRequest` handler processes HTTP requests sent to your actor. It receives the actor context and a standard `Request` object and returns a `Response` object. ### Raw HTTP ```typescript import { actor } from "rivetkit"; export const counterActor = actor({ state: { count: 0, }, // WinterTC compliant - accepts standard Request and returns standard Response onRequest: (c, request) => { const url = new URL(request.url); if (request.method === "GET" && url.pathname === "/count") { return Response.json({ count: c.state.count }); } if (request.method === "POST" && url.pathname === "/increment") { c.state.count++; return Response.json({ count: c.state.count }); } return new Response("Not Found", { status: 404 }); }, actions: {} }); ``` ### Hono ```typescript import { actor, ActorContextOf } from "rivetkit"; import { Hono } from "hono"; // Define the actor first const counterActor = actor({ state: { count: 0 }, actions: {} }); // Build router with typed context function buildRouter(actorCtx: ActorContextOf) { const app = new Hono(); app.get("/count", (honoCtx) => { return honoCtx.json({ count: actorCtx.state.count }); }); app.post("/increment", (honoCtx) => { actorCtx.state.count++; return honoCtx.json({ count: actorCtx.state.count }); }); return app; } // Define the full actor with onRequest export const counterActorWithRouter = actor({ state: { count: 0 }, vars: { app: null as Hono | null }, createVars: () => ({ app: null as Hono | null }), onRequest: async (c, request) => { // Build router lazily const app = buildRouter(c as ActorContextOf); return await app.fetch(request); }, actions: {} }); ``` See also the [raw fetch handler example](https://github.com/rivet-dev/rivet/tree/main/examples/raw-fetch-handler). ## Sending Requests To Actors ### Via RivetKit Client Use the `.fetch()` method on an actor handle to send HTTP requests to the actor's `onRequest` handler. This can be executed from either your frontend or backend. ```typescript index.ts @hide import { actor, setup } from "rivetkit"; export const counter = actor({ state: { count: 0 }, onRequest: (c, request) => { if (request.method === "POST") c.state.count++; return Response.json(c.state); }, actions: {} }); export const registry = setup({ use: { counter } }); registry.start(); ``` ```typescript client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./index"; const client = createClient("http://localhost:6420"); const actor = client.counter.getOrCreate(["my-counter"]); // .fetch() is WinterTC compliant, it accepts standard Request and returns standard Response const response = await actor.fetch("/"); const data = await response.json(); console.log(data); // { count: 0 } ``` ### 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 HTTP clients. ```typescript index.ts @hide import { actor, setup } from "rivetkit"; export const counter = actor({ state: { count: 0 }, onRequest: (c, request) => { if (request.method === "POST") c.state.count++; return Response.json(c.state); }, actions: {} }); export const registry = setup({ use: { counter } }); registry.start(); ``` ```typescript client.ts import { createClient } from "rivetkit/client"; import type { registry } from "./index"; const client = createClient("http://localhost:6420"); const actor = client.counter.getOrCreate(["my-counter"]); // Get the raw gateway URL const gatewayUrl = await actor.getGatewayUrl(); // gatewayUrl = "https://...rivet.dev/..." // Use with native fetch const response = await fetch(`${gatewayUrl}/request/`); const data = await response.json(); console.log(data); // { count: 0 } ``` ### Via HTTP API This handler can be accessed with raw HTTP using `https://api.rivet.dev/gateway/{actorId}/request/{...path}`. For example, to call `POST /increment` on the counter actor above: ```typescript // Replace with your actor ID and token const actorId = "your-actor-id"; const token = "your-token"; const response = await fetch( `https://api.rivet.dev/gateway/${actorId}/request/increment`, { method: "POST", headers: { "x-rivet-token": token, }, } ); const data = await response.json(); console.log(data); // { count: 1 } ``` ```bash curl -X POST "https://api.rivet.dev/gateway/{actorId}/request/increment" \ -H "x-rivet-token: {token}" ``` The request is routed to the actor's `onRequest` handler where: - `request.method` is `"POST"` - `request.url` ends with `/increment` (the path after `/request/`) - Headers, body, and other request properties are passed through unchanged See the [HTTP API reference](/docs/actors/http-api) for more information on HTTP routing and authentication. ### Via Proxying Requests You can proxy HTTP requests from your own server to actor handlers using the RivetKit client. This is useful when you need to add custom authentication, rate limiting, or request transformation before forwarding to actors. ```typescript import { Hono } from "hono"; import { createClient } from "rivetkit/client"; import { serve } from "@hono/node-server"; const client = createClient(); const app = new Hono(); // Proxy requests to actor's onRequest handler app.all("/actors/:id/:path{.*}", async (c) => { const actorId = c.req.param("id"); const actorPath = (c.req.param("path") || ""); // Rewrite the incoming request to the actor-relative path, preserving // method, headers, and body const url = new URL(actorPath, "http://actor"); const actorRequest = new Request(url, c.req.raw); // Forward the rewritten Request to the actor's onRequest handler const actor = client.counter.get(actorId); return await actor.fetch(actorRequest); }); serve(app); ``` ## Connection & Lifecycle Hooks `onRequest` will trigger the `onBeforeConnect`, `onConnect`, and `onDisconnect` hooks. Read more about [lifecycle hooks](/docs/actors/lifecycle). Requests in flight will be listed in `c.conns`. Read more about [connections](/docs/actors/connections). ## WinterTC Compliance The `onRequest` handler is WinterTC compliant and will work with existing libraries using the standard `Request` and `Response` types. ## Limitations - Does not support streaming responses & server-sent events at the moment. See the [tracking issue](https://github.com/rivet-dev/rivet/issues/3529). - `OPTIONS` requests currently are handled by Rivet and are not passed to `onRequest` ## Advanced ### Skip Ready Wait Requests are normally held at the gateway until the actor is ready. Pass `skipReadyWait: true` on `handle.fetch()` to deliver 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. ## API Reference - [`RequestContext`](/typedoc/interfaces/rivetkit.mod.RequestContext.html) - Context for HTTP request handlers - [`ActorDefinition`](/typedoc/interfaces/rivetkit.mod.ActorDefinition.html) - Interface for defining request handlers _Source doc path: /docs/actors/request-handler_