HTTP Endpoints
worlds.HTTPHandler(world, prefix), which every World package wraps as HTTP(world), serves two endpoints under a prefix (default /v1/workflow). It does not execute deliveries.
| Route | Handler | Purpose |
|---|---|---|
POST <prefix>/rpc | worlds.RPCHandler | CBOR RPC for clients and tools |
<prefix>/webhooks/{token} | worlds.WebhookHandler | webhook requests |
The Vercel World adds POST <prefix>/callbacks/qstash when QStash is configured. vercelworld.QStash(world) exposes that callback separately. Callback routes under <prefix>/callbacks/ are where a World mounts endpoints specific to it.
RPC
The RPC endpoint exposes the World and Client APIs. workflow.NewHTTPClient and the world-http adapter use it.
Request:
POSTwithContent-Type: application/cbor(other types get415), a body of at most 8 MiB:{ method: string, input: <cbor>, specVersion?: int }Response:
200withContent-Type: application/cbor, including when the method fails:{ output?: <cbor>, error?: { code: string, message: string, sentinel?: string } }Error codes are
not_found,invalid_argument,conflict,cancelled,deadline_exceeded, andinternal.sentinelnames theworldssentinel error the failure matches, such asrun_not_found, soworkflow.NewHTTPClientreturns errors that matcherrors.Isas they would in process.Waits:
runs.waitForTerminalStatusandstreams.getChunkswait at most 30 seconds per request. Clients repeat the request to wait longer.Spec version: when
specVersionis set and differs from the World's (worlds.SpecVersion, currently1), the request fails withinvalid_argument. Clients callclient.specfirst, which returns{ specVersion, capabilities }.
Methods
| Group | Methods |
|---|---|
| World | world.deploymentId |
| Runs | runs.get, runs.list, runs.waitForTerminalStatus, runs.setAttributes |
| Steps | steps.get, steps.list |
| Events | events.create, events.createRun, events.createBatch, events.get, events.list, events.listByCorrelationID |
| Hooks | hooks.get, hooks.getByToken, hooks.list |
| Queue | queue.queue, queue.queueBatch, queue.cancel |
| Streams | streams.write, streams.createWriteSession, streams.writeSession, streams.closeWriteSession, streams.writeMulti, streams.close, streams.getChunks, streams.getInfo, streams.list |
| Analytics | analytics.runs.get, analytics.runs.list, analytics.attributes.list, analytics.steps.get, analytics.steps.list, analytics.events.get, analytics.events.getMany, analytics.events.list, analytics.events.listByCorrelationId, analytics.hooks.get, analytics.hooks.list, analytics.waits.get, analytics.waits.list |
| Client | client.spec, client.runs.create, client.runs.cancel, client.runs.wake, client.streams.write, client.streams.writeMulti, client.streams.close, client.hooks.resume |
Most methods return the World's Go types. The event writes return the Workflow SDK's result shapes instead: events.create and events.createRun return worlds.RPCEventResult, with the committed event and the run, step, or hook it changed, and events.createBatch returns worlds.RPCEventBatchResult, with one outcome per event. A batch the World refuses without an in-band fence is applied one event at a time, so each event reports its own status. Other batch failures are returned as the request's error. A run created without an ID is given one.
Unauthenticated
The RPC endpoint can start runs, append events, and read every payload. Never expose it publicly unless worlds.WithRPCToken or worlds.WithRPCMiddlewareprotects it.
Webhooks
<prefix>/webhooks/{token} resumes the webhook that owns token with the request.
| Situation | Response |
|---|---|
| No active webhook owns the token, or its run finished | 404 Not Found |
Method differs from WebhookOption.Method | 405 Method Not Allowed with Allow |
| Body over 128 KiB | 413 Request Entity Too Large |
| Default webhook | 202 Accepted |
WebhookOption.Response set | that response |
WebhookOption.Manual | Waits for a step to call WebhookRequest.RespondWith. |
| Manual response times out | 504 Gateway Timeout |
| Webhook disposed or run terminated while waiting for a response | 410 Gone |
| Storage failure while waiting for a response | 500 Internal Server Error |
| Ownership changes during the request | Retryable 503 Service Unavailable. The request is not forwarded to the new owner. |
The request's method, URL, headers, and body are recorded in a hook_received event and a delivery is queued so the run resumes.
The token is the only authorization. Serve WebhookHandler (or a World's Webhook(world)) publicly on its own if you need webhooks but want to keep RPC private.
Mounting under another path
mux := http.NewServeMux()
mux.Handle("/v1/workflow/", postgresworld.HTTP(world))
mux.Handle("/", app)Or with a custom prefix, using the World package's HTTPPrefix (or worlds.HTTPHandler for your own World):
handler := postgresworld.HTTPPrefix(world, "/internal/workflow")Whatever you choose, set the World's Config.BaseURL to the public URL of that prefix so webhook URLs are correct, and pass the same URL to workflow.NewHTTPClient.