ExecWarden Docs

Event ingress

Turn signed HTTP callbacks into workflow events.

Webhook receivers verify incoming requests, run a small JavaScript handler, and publish normalized events that workflows subscribe to by source and type.

When to Use Webhooks

Use a webhook when another system should start work and can send a signed HTTP callback. Keep provider parsing in the receiver handler, emit a small normalized event, then let a workflow decide what happens next with scoped capabilities and approvals.

Good fitIncidents, billing events, CI notifications, support escalations, or GitHub events that should start a bounded investigation or follow-up.
Not the jobDo not put the whole business process in the handler. The handler verifies, normalizes, emits, accepts, ignores, or rejects.
Next stepCreate or inspect the receiver first, then use Workflows for the repeatable run that consumes the event.

Mental Model

Webhooks are an ingress path, not workflow triggers by themselves. A receiver verifies the request, runs a short JavaScript handler in the safe Wasm JS runtime, and the handler may emit one or more workflow events. Workflows subscribe to those emitted events by source and eventType.

ReceiverThe public endpoint and secret. GitHub-preset receivers use GitHub signatures; custom receivers default to handler verification for provider-specific signatures.
HandlerA small async JavaScript function that can parse provider payloads and emit only { type, idempotencyKey, value }.
EventThe normalized record stored by ExecWarden. The receiver supplies source; the handler supplies type, idempotencyKey, and value.
Workflow triggerA saved workflow trigger such as { kind: "event", source: "webhook:incident-router", eventType: "incident.created" }.

Set It Up Through an Agent

The Webhooks page opens a setup agent with webhook management context. For an end-to-end setup, give the session concrete webhook receiver and workflow grants, then ask it to fetch the focused guides before saving anything.

Create a custom webhook receiver for our incident router. It should accept signed JSON payloads, emit incident.created events, simulate the handler with a sample payload, then create an enabled workflow that subscribes to that event and starts an investigation run. Use source webhook:incident-router and event type incident.created. Use the provider delivery id header x-incident-event-id as the idempotency key. Verify x-incident-signature as HMAC-SHA-256 of the raw body with the receiver secret.
GuidesAllow the webhook and workflow guide capabilities so the setup agent can fetch focused instructions.
InspectAllow receiver list/get and workflow validation so the agent can compare before saving.
Create receiverAllow receiver creation for a new ingress path.
Update receiverKeep receiver updates in Ask mode so handler changes pause before saving.
SimulateAllow simulation before the receiver handles live provider deliveries.
Create workflowKeep workflow creation in Ask mode when the setup agent will save repeatable work.

The agent should call gateway:webhook_setup_guide, list or get existing receivers, create or update the receiver, simulate the handler, call gateway:workflow_guide, validate the workflow definition, and then save the workflow.

Custom Receiver Example

A custom receiver documents its expected event types for people and agents. The handler still owns the real event emission, so keep the script small and normalize provider payloads before workflows consume them.

async (input, webhook) => { const expected = "sha256=" + webhook.crypto.hmacSha256Hex(input.receiver.secret, input.rawBodyText); const actual = input.headers["x-incident-signature"] || ""; if (!webhook.crypto.timingSafeEqual(actual, expected)) { return webhook.reject(401, "invalid signature"); } if (input.bodyParseError) { return webhook.reject(400, input.bodyParseError); } const body = input.body && typeof input.body === "object" ? input.body : {}; const incidentId = typeof body.id === "string" ? body.id : null; const eventId = input.headers["x-incident-event-id"]; if (!incidentId) { return webhook.reject(400, "missing incident id"); } if (!eventId) { return webhook.reject(400, "missing incident event id"); } webhook.emit({ type: "incident.created", idempotencyKey: eventId, value: { incidentId, severity: body.severity ?? "unknown", title: body.title ?? "Untitled incident", receiverId: input.receiver.id } }); return webhook.accept(); }
Sourcewebhook:incident-router
Documented event types["incident.created"]
Signature headerx-incident-signature
IdempotencyPrefer a provider delivery id. If none exists, choose an explicit key that matches the duplicate behavior you want.

Workflow Subscription Example

The workflow receives the handler's value as ctx.input.event. It also receives trigger metadata under ctx.input.trigger.

{ "name": "Incident webhook investigation", "enabled": true, "triggers": [ { "id": "incident-created", "kind": "event", "source": "webhook:incident-router", "eventType": "incident.created", "enabled": true } ], "definition": { "runtime": "graph", "startStepId": "main", "steps": [ { "id": "main", "kind": "wasm-js", "entrypoint": "main", "source": "export function main(ctx) { return { ok: true, incidentId: ctx.input.event.incidentId, severity: ctx.input.event.severity }; }" } ] } }

GitHub Preset Example

Use the GitHub preset when the producer is GitHub. ExecWarden verifies x-hub-signature-256, readsx-github-event and payload action, then emits source: "github" events such as pull_request.opened, pull_request.synchronize, push, and workflow_job.queued.

{ "triggers": [ { "id": "pr-opened", "kind": "event", "source": "github", "eventType": "pull_request.opened", "enabled": true } ] }

Observability and Limits

SimulationUse gateway:webhook_receivers.simulate before saving risky handler changes. Simulation skips host pre-verification, but handler-verifier code still runs; include representative signed headers when testing custom verification.
ReplayUse gateway:webhook_deliveries.replay to test a stored live delivery, optionally with a draft script.
Event valuesReceiver readers can see delivery status and emitted event ids/types. Payload sample values are shown only to users with webhook management permission.
DedupeWorkflow events dedupe by workspace, source, and handler-provided idempotency key. Event-triggered workflow starts are also idempotent per event/trigger pair.
Follow-up surfaceDirect UI editing of receiver code, richer delivery pagination, and more maintained producer presets are still maturing.
Next questionUse Workflows for event delivery, idempotency, run recovery, and approval behavior after the event is emitted.