oRPC
Call the oRPC endpoint at /api/rpc with a service key to manage webhooks.
Litestore mounts oRPC at /api/rpc, and outside services call it with a service key. Use it when your integration needs to manage webhooks or check that the store is up.
Request
oRPC offers a router for managing webhooks and a health ping. Admin and storefront writes don't go through oRPC.
Each kind of caller has one way in. An integration with a service key uses oRPC. The admin and the storefront use zsa server actions. The published contract in app/api/openapi.json describes the REST endpoints. When the same operation exists in two transports, the two versions can drift apart.
The comment at the top of the router file states this boundary:
/**
* Scope is deliberately narrow: oRPC is the EXTERNAL service-key API surface
* (AI agents / integrations managing webhooks), plus a health ping. Admin and
* storefront writes are zsa server actions in `modules/<area>/{admin,storefront}/actions.ts`
* — the one transport story per consumer. A parallel oRPC admin write surface
* was deleted: it had no client in this repo and is not part of the published
* OpenAPI contract (app/api/openapi.json describes the REST endpoints instead).
*/
export const appRouter = {
ping,
web: webRouter,
}Procedures
webRouter in platform/api/storefront-router.ts has exactly one router today, webhooks from modules/integrations/webhooks/router.ts.
| Procedure | Scope required | What it does |
|---|---|---|
web.webhooks.list | read | Lists webhooks with id, url, events, isActive, name and createdAt |
web.webhooks.get | read | Returns one webhook by id, or null |
web.webhooks.create | write:webhooks | Creates a webhook and returns its secret once |
web.webhooks.update | write:webhooks | Updates the url, events and name |
web.webhooks.delete | write:webhooks | Removes the webhook |
web.webhooks.rotateSecret | write:webhooks | Issues a new secret and returns it once |
ping | none | Returns { status: "ok", timestamp } |
Save the secret from the response
Create and rotate return the signing secret in the response body, because the secret isn't readable later. If your code throws away the create response, it loses the secret for good and has to rotate to get a new one.
Procedure builders
platform/api/orpc.ts stacks three procedures, and each one builds on the one before.
baseProcedure
It puts db and a revalidate helper on the context.
serviceProcedure
It looks up the service key and turns an AppError into an ORPCError.
serviceProcedureWithScope
It does nothing except check the scope:
export function serviceProcedureWithScope(scope: ApiScope) {
return serviceProcedure.use(async ({ next, context }) => {
if (!hasScope(context.apiKey.scopes, scope)) {
throw new ORPCError("FORBIDDEN", {
message: `Requires ${scope} scope`,
})
}
return next()
})
}Because the layers stack, a procedure can't get a database handle until Litestore has resolved the key. Every procedure is authenticated, and none can opt out.
Admin access
oRPC has no session-auth or permission procedures and no admin router, so no admin write is reachable through it. The procedures that would accept an admin session don't exist in platform/api/orpc.ts.
One webhook lifecycle
The oRPC router and the admin's Settings → Developer screen both call the same webhook lifecycle in modules/integrations/webhooks/lifecycle.ts, so a webhook created over oRPC and one created in the admin go through the same code.
Transports
Use oRPC when an outside integration with a service key needs to manage webhooks or run a health check. Those are the only jobs it covers.
Before you add a procedure here, don't ask whether it would be convenient over RPC. Ask this instead: does this operation belong to a service key, and does the published contract not already cover it?
Related
Overview
Admin and storefront writes use zsa server actions, service-key callers use oRPC at /api/rpc, and /api/openapi.json describes the REST endpoints.
Keys
Litestore mints, hashes and revokes service API keys, and each enforced scope permits a specific set of calls.
Webhooks
Litestore signs and retries outbound webhook deliveries, disables an endpoint that keeps failing, and receives Stripe and Resend callbacks on separate routes.