JS Webhooks
Webhook endpoints serve HTTP requests. The handler receives a Request object and must return a
Response. Webhooks can call activities and workflows via obelisk.call and make outbound HTTP
requests via fetch. See
Webhook Endpoints for the general
model.
[[webhook_endpoint_js]]
name = "my_webhook"
location = "${DEPLOYMENT_DIR}/webhook/my_webhook.js"
routes = [
{ methods = ["GET"], route = "/items" },
{ methods = ["POST"], route = "/items/:id" },
]
env_vars = [{ key = "DB_URL", value = "..." }]
[[webhook_endpoint_js.allowed_host]]
pattern = "https://api.example.com"
methods = ["GET"]
Route path segments starting with : are captured and exposed as process.env entries inside the
handler — process.env['segment-name'] returns the captured value as a string.
Handler signature
export default function handle(request) {
// sync or async
return new Response("body", { status: 200, headers: { "content-type": "text/plain" } });
}Request object
| Property / method | Description |
|---|---|
request.url | Full request URL as a string |
request.method | HTTP method ("GET", "POST", …) |
request.headers | Headers object |
request.headers.get(name) | Returns header value or null; multiple values joined by ", " |
Response construction
// Text response
return new Response("Hello!", { status: 200, headers: { "content-type": "text/plain" } });
// JSON response (sets content-type: application/json automatically)
return Response.json({ key: "value" });
return Response.json([1, 2, 3], { status: 201 });
// Proxy a fetch response directly
const resp = await fetch("https://api.example.com/data");
return resp;Calling activities and workflows
Webhooks have access to obelisk.call — the call blocks until the child execution completes, so the
HTTP response is only sent after the workflow finishes.
// ffqn: myapp:demo/webhook.handle
export default function handle(request) {
const url = new URL(request.url);
if (url.pathname === "/serial") {
const result = obelisk.call("myapp:demo/workflow.serial", []);
return new Response(`serial completed: ${result}`, { status: 200 });
}
return new Response("not found", { status: 404 });
}Path parameters
When a route contains named segments (:name), the captured values are available as process.env
entries inside the handler:
[[webhook_endpoint_js]]
name = "my_webhook"
location = "${DEPLOYMENT_DIR}/webhook/my_webhook.js"
routes = ["/users/:user-id/items/:item-id"]export default function handle(request) {
const userId = process.env["user-id"];
const itemId = process.env["item-id"];
return Response.json({ userId, itemId });
}Scheduling executions
Webhooks can schedule top-level executions without blocking the HTTP response. First generate an
execution ID, then pass it to obelisk.schedule:
export default function handle(request) {
const execId = obelisk.generateExecutionId();
obelisk.schedule(execId, "myapp:demo/activity.send-email", ["user@example.com"]);
// optional schedule-at: obelisk.schedule(execId, ffqn, args, { seconds: 60 });
return new Response(execId, { status: 202 });
}
Check or retrieve a previously scheduled or running execution:
const status = obelisk.getStatus(execId);
// e.g. { status: "pendingAt" } or { status: "finished" }
const result = obelisk.tryGet(execId);
// { pending: true } if not finished yet
// { ok: value } or { err: value } when done
Webhook scheduling API:
| Call | Returns | Description |
|---|---|---|
obelisk.generateExecutionId() | executionId (string) | Generate a unique execution ID |
obelisk.schedule(execId, ffqn, args) | — | Schedule a top-level execution immediately |
obelisk.schedule(execId, ffqn, args, delay) | — | Schedule at a delay (e.g. { seconds: 60 }) |
obelisk.getStatus(execId) | { status: string } | Get the current status of an execution |
obelisk.tryGet(execId) | { pending: true } or { ok/err: value } | Non-blocking result fetch |
Available globals
Same as JS activities: process.env, fetch,
crypto.subtle, console, TextEncoder/TextDecoder.
Examples
Read a request header:
export default function handle(request) {
const value = request.headers.get("x-custom");
return Response.json(value !== null ? value.split(", ") : []);
}
Read environment variable:
export default function handle(_request) {
const value = process.env["API_KEY"];
if (value === undefined) return new Response("missing config", { status: 500 });
return new Response(value, { status: 200 });
}
Proxy an outbound fetch:
export default async function handle(request) {
// Requires [[webhook_endpoint_js.allowed_host]] for the target
const resp = await fetch("https://api.example.com/data", {
headers: { accept: "application/json" },
});
return resp;
}
Forward request headers to a fetch:
export default async function handle(request) {
return fetch("https://api.example.com/data", { headers: request.headers });
}