The answer. A Service Binding is a direct handle from one Worker to another on the same account. The call never leaves Cloudflare, is not counted as a request, and by default both Workers run on the same thread of the same machine, so the added latency is effectively zero. Two shapes: env.AUTH.fetch(request) when the callee speaks HTTP, or plain method calls when the callee extends WorkerEntrypoint and exposes methods (RPC). The callee stays private by having no workers.dev URL (workers_dev = false), no route and no custom domain; only Workers holding a binding can reach it. This is the correct answer to "how do I call my other Worker", not a public hostname on your own zone.
The pattern.
// caller wrangler.jsonc
{ "services": [{ "binding": "AUTH", "service": "auth-service-production", "entrypoint": "AuthApi" }] }
// callee: auth-service, exports a named entrypoint with RPC methods
import { WorkerEntrypoint } from "cloudflare:workers";
export class AuthApi extends WorkerEntrypoint<Env> {
async verify(token: string): Promise<{ sub: string } | null> { /* ... */ return null; }
}
export default { fetch: () => new Response("private", { status: 404 }) };
// caller
const who = await env.AUTH.verify(bearer); // looks local, is an RPC across the binding
if (!who) return new Response("unauthorized", { status: 401 });
Watch out.
servicemust match the deployed Worker name of that environment (name-staging,name-production), not the top-level name.- Bodies are read once. Forward
request.clone()if you also read it, or the callee gets an empty stream. - RPC hides the network. Arguments are serialized (structured clone), stubs are remote, and detaching a method with
.bind()calls a remote method named "bind" (see the Workflows entry).
Related: workflows-step-do-is-an-rpc-receiver · worker-fetch-own-zone-error-1042 · custom-domains-vs-routes-who-is-the-origin