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.
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:
| Mode | How callers authenticate | Use when |
|---|---|---|
| Bearer | Authorization: Bearer <secret> header; no body verification. | The caller can hold a secret header but not sign payloads. |
| HMAC | X-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/…/…" }
| Status | Meaning |
|---|---|
202 | Run created. |
400 | Body isn’t valid JSON, or isn’t a JSON object. |
401 | Authentication failed — wrong secret or bad signature. |
404 | Unknown trigger ID. |
409 | The trigger is disabled, or the agent itself is disabled. |
413 | Body 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.