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 / methodDescription
request.urlFull request URL as a string
request.methodHTTP method ("GET", "POST", …)
request.headersHeaders 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:

CallReturnsDescription
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 });
}