Webhooks
Receive HTTP callbacks from external services. Declare a webhook in your manifest, deploy, and your app gets a unique URL that routes incoming requests to your backend worker.
The RootCX skill for Claude Code handles the full setup for you. Tell it what you need and it wires the manifest, backend handler, and frontend in one pass:
Add a webhook called "stripe" that stores payment events and shows them in a table
How It Works
When you declare a webhook in manifest.json and deploy, the Core generates a unique 64-character token. External services POST to /api/v1/hooks/{token}. Core resolves the token to your app and invokes the specified RPC method on your worker with the full request data.
No authentication header is required on incoming requests. The token itself authenticates the caller. Tokens are stable across re-deploys.
Manifest Declaration
Add a webhooks array to your manifest.json. Each entry maps a name to a backend RPC method.
{
"webhooks": [
{ "name": "stripe", "method": "onStripePayment" },
{ "name": "github", "method": "onGithubPush" }
]
}
| Field | Description |
|---|---|
name |
Unique identifier for this webhook. Used to retrieve its URL after deploy. |
method |
RPC method invoked on your backend worker when a request arrives. |
Removing a webhook from the manifest deletes it (and its token) on next deploy.
Worker Handler
Your worker receives a standard RPC call with four parameters:
serve({
rpc: {
onStripePayment: async (params, _caller, ctx) => {
// params.name - webhook name ("stripe")
// params.headers - HTTP headers as { key: value }
// params.body - parsed JSON (or string if not valid JSON)
// params.rawBody - base64-encoded raw request body
await ctx.collection("payment_events").insert({
source: params.name,
headers: params.headers,
payload: params.body,
received_at: new Date().toISOString(),
});
return { ok: true };
},
},
});
The return value of your handler is sent back as the HTTP response to the calling service.
Ingress Endpoint
POST /api/v1/hooks/{token}
No Authorization header required. The token in the URL authenticates the request.
curl -X POST https://your-core.rootcx.com/api/v1/hooks/02b0bd3c...64chars \
-H "Content-Type: application/json" \
-d '{"event": "payment.completed", "amount": 4999}'
Retrieve Webhook URLs
Tokens are generated by Core on deploy. Retrieve your webhook URLs via the management API:
GET /api/v1/apps/{appId}/webhooks
Requires app:{appId}:webhook.read permission.
[
{
"id": "uuid",
"name": "stripe",
"method": "onStripePayment",
"token": "02b0bd3cf466...64 chars",
"url": "/api/v1/hooks/02b0bd3cf466...",
"createdAt": "2026-05-13T08:38:31Z"
}
]
From the frontend SDK:
const client = useRuntimeClient();
const res = await fetch(`/api/v1/apps/${APP_ID}/webhooks`, {
headers: { Authorization: `Bearer ${client.accessToken}` },
});
const webhooks = await res.json();
Signature Verification
For production webhooks (Stripe, GitHub, etc.), verify the signature using rawBody:
import { createHmac } from "crypto";
onStripePayment: async (params, _caller, ctx) => {
const sig = params.headers["stripe-signature"];
const raw = Buffer.from(params.rawBody, "base64");
const expected = createHmac("sha256", STRIPE_SECRET)
.update(raw)
.digest("hex");
if (sig !== expected) {
return { error: "invalid signature" };
}
// Process verified payload...
}
CLI Workflow
# Create a new app
rootcx new my-webhook-app && cd my-webhook-app
# Edit manifest.json: add webhooks array
# Edit backend/index.ts: add RPC handler
# Deploy (registers webhook, generates token)
rootcx deploy
# Check status
rootcx status
Or let the skill do it all:
Create a GitHub webhook handler with signature verification that logs push events
REST API
See the REST API Reference for all webhook management endpoints.