Receive webhooks
In this example, we are going to give each customer an HTTPS endpoint that receives and stores webhooks in their own deployment. A Command lets your product read the events it needs without exposing a second admin API.
This is useful when webhook payloads contain customer data that should be processed and retained in the customer's environment. GitHub, Stripe, or another sender still uses an ordinary public URL; your hosted backend does not need the KV credential or a route into the customer's network.
The two entry points serve different callers. GitHub, Stripe, or another external service sends an ordinary HTTP request. Your backend invokes a Command for a specific deployment when it needs the recorded events.
An external service sends an HTTPS webhook to a Worker in the customer environment. The Worker verifies and stores the event, then returns an HTTP response.
We will describe the Worker, public endpoint, and KV store in alien.ts. Then we will implement the webhook route and the Command in src/index.ts.
Describe the endpoint and storage
const events = new alien.Kv("events").build()
const api = new alien.Worker("api")
.code({ type: "source", src: "./", toolchain: { type: "typescript" } })
.commandsEnabled(true)
.publicEndpoint("api")
.link(events)
.permissions("execution")
.build().link(events) gives this Worker access to the deployment's KV resource. The execution permission profile becomes the cloud permissions for that access; the hosted control plane never receives the KV credential.
KV is a small key-value store created for each deployment. This example uses keys such as github:1724450000000, which makes recent events easy to scan by source without adding a database service.
Receive and store a webhook
app.post("/webhooks/:source", async c => {
const source = c.req.param("source")
const body = await c.req.json()
const key = `${source}:${Date.now()}`
await kv("events").setJson(key, {
source,
body,
receivedAt: new Date().toISOString(),
})
return c.json({ received: true, key })
})The full example also defines a signing-secret input. Verify the sender's signature before storing real webhooks.
Let your product read the events
command("get-events", querySchema, async ({ source, limit }) => {
const prefix = source ? `${source}:` : ""
return scanEvents(kv("events"), { prefix, limit })
})Run it locally
alien init webhook-api-ts
alien dev
curl -X POST http://localhost:<port>/webhooks/github -H 'content-type: application/json' -d '{"action":"opened"}'The port is printed by alien dev.
Read the complete source for cursor-based KV scanning, input validation, and webhook configuration.
Give each customer their own webhook endpoint
alien releasePublishes a version. Nothing is deployed for a customer yet.
alien onboard acme-corpCreates a deployment link for that customer.
The customer opens the link and deploys into their environment.
Each deployment receives its own HTTPS endpoint and KV store. Your control plane can query that deployment's events through Commands.
What you built
You built a customer-specific webhook receiver whose payloads are accepted and stored in the customer's cloud. Outside senders use HTTPS, while your product uses a narrow Command interface to inspect results without receiving storage credentials or exposing an administrative port.
Source: examples/webhook-api-ts.