DocsPlatformWebhooks

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.