Skip to content

Webhooks

A webhook trigger is an authenticated endpoint that starts one Task Agent’s runs from outside Lunen — CI pipelines, alerting systems, form backends, anything that can POST. The run it creates is a normal run: same plan, same policy, same approvals, same audit trail.

Updated 30 July 2026 · 5 min read
Note

Webhooks are inbound only. Lunen does not currently deliver outbound webhooks — there is no subscription for run or approval events.

Create a trigger

Triggers live in the Webhook Triggers section of a Task Agent’s blueprint panel. Each trigger gets its own URL and secret:

POST https://<your-lunen-host>/hooks/agents/task/{triggerId}

Choose the authentication mode at creation:

ModeHow callers authenticateUse when
BearerAuthorization: Bearer <secret> header; no body verification.The caller can hold a secret header but not sign payloads.
HMACX-Lunen-Signature: sha256=<hex> — HMAC-SHA256 of the raw request body with the trigger’s key.You send a payload and want it tamper-evident.

Triggers can be disabled without being deleted, and secrets rotate from the same panel — the previous secret keeps working for a 24-hour grace period so callers migrate without a hard cut. Creation, disabling, and rotation are all audit events.

Send a payload

An HMAC trigger’s request body must be a JSON object, up to 1 MiB. It becomes the run’s run input, delivered to the plan steps marked as consuming it — how an alert’s details or a form submission become the material the run works on. Bearer triggers take no body; they just fire the run.

BODY='{"incident":"sev2-4123","service":"checkout"}'
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$WEBHOOK_KEY" -r | cut -d' ' -f1)
curl -X POST "https://<your-lunen-host>/hooks/agents/task/$TRIGGER_ID" \
  -H "Content-Type: application/json" \
  -H "X-Lunen-Signature: sha256=$SIG" \
  -d "$BODY"

Responses

Success is 202 Accepted — the run is created and queued, not awaited:

{ "runId": "…", "runUrl": "https://…/app/agents/task/…/…" }
StatusMeaning
202Run created.
400Body isn’t valid JSON, or isn’t a JSON object.
401Authentication failed — wrong secret or bad signature.
404Unknown trigger ID.
409The trigger is disabled, or the agent itself is disabled.
413Body over 1 MiB.

Semantics worth designing around

A 202 means the run exists, not that it will succeed — follow runUrl for the outcome. Run-level guarantees apply as usual: if a run for the agent is already active, the new run is skipped, so a webhook storm becomes a series of skips rather than a pile-up; and a triggered run that hits an Approve-mode tool pauses for a human like any other. Callers wanting delivery certainty should treat non-202 responses as theirs to retry — Lunen does not queue failed deliveries.