AgentOS can notify your server when key events happen — a run completes, fails, or needs human input. Configure a webhook URL in your agent settings and AgentOS will POST a signed payload to your endpoint.
Webhook delivery is best-effort with up to 3 retries on failure (exponential backoff). For guaranteed processing, acknowledge quickly and handle async.
| Event | When it fires |
|---|---|
run.completed | A run finishes successfully |
run.failed | A run fails with an error |
run.started | A run is created (useful for tracking) |
approval.requested | An agent is waiting for human approval |
alert.triggered | An alert condition was met |
Every webhook POST has the same envelope:
{
"event": "run.completed",
"timestamp": "2026-05-17T10:00:00.000Z",
"workspace_id": "uuid",
"agent_id": "uuid",
"data": { ... }
}
run.completed{
"event": "run.completed",
"timestamp": "2026-05-17T10:00:00.000Z",
"workspace_id": "uuid",
"agent_id": "uuid",
"data": {
"run_id": "uuid",
"trigger_type": "scheduled",
"started_at": "2026-05-17T09:59:55.000Z",
"completed_at": "2026-05-17T10:00:00.000Z",
"duration_ms": 5000,
"output": { "summary": "..." }
}
}
run.failed{
"event": "run.failed",
"data": {
"run_id": "uuid",
"error_message": "API timeout after 3 retries",
"started_at": "2026-05-17T09:59:55.000Z",
"failed_at": "2026-05-17T10:00:00.000Z"
}
}
approval.requested{
"event": "approval.requested",
"data": {
"run_id": "uuid",
"approval_id": "uuid",
"tool": "send_email",
"input": { "to": "ceo@company.com", "subject": "Q1 Report" },
"expires_at": "2026-05-17T11:00:00.000Z"
}
}
Every request includes an X-AgentOS-Signature header — a HMAC-SHA256 signature of the raw request body using your webhook secret.
Always verify the signature before processing a webhook. Without verification, anyone can send fake events to your endpoint.
import { createHmac } from "crypto";
export function verifyWebhook(
rawBody: string,
signature: string,
secret: string,
): boolean {
const expected = createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
return `sha256=${expected}` === signature;
}
// Express example
app.post("/webhooks/agentos", express.raw({ type: "application/json" }), (req, res) => {
const sig = req.headers["x-agentos-signature"] as string;
if (!verifyWebhook(req.body.toString(), sig, process.env.WEBHOOK_SECRET!)) {
return res.status(401).send("Invalid signature");
}
const event = JSON.parse(req.body.toString());
// handle event...
res.status(200).send("ok");
});
import hmac, hashlib
def verify_webhook(raw_body: bytes, signature: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(
secret.encode(), raw_body, hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
Use hmac.compare_digest (Python) or a constant-time comparison (Node.js crypto.timingSafeEqual) to prevent timing attacks.
Return a 2xx status within 10 seconds. AgentOS marks delivery as failed and retries if it receives a non-2xx response or the connection times out.
Do the minimum work synchronously — acknowledge immediately and enqueue the rest:
app.post("/webhooks/agentos", async (req, res) => {
// verify signature first
if (!verifyWebhook(...)) return res.status(401).send();
// acknowledge immediately
res.status(200).send("ok");
// process async
await queue.enqueue(req.body);
});
Your webhook secret is shown once when you save the webhook URL. Store it as an environment variable:
AGENTOS_WEBHOOK_SECRET=whsec_...
To rotate the secret, delete and re-add the webhook URL in agent settings. Update your environment variable before removing the old webhook to avoid downtime.