--- title: Webhook notifier description: Generic HTTP POST. Retries, exponential backoff, custom headers. ---
Slot: notifier ยท Name: webhook
Point it at any HTTPS endpoint. AO POSTs a JSON event. Use this for PagerDuty-style integrations, custom dashboards, or anything we don't have a dedicated notifier for. ## Setup Run the setup command: ```bash ao setup webhook ``` If a webhook notifier is already configured, the command asks whether to use the existing webhook URL, add a new webhook URL, or cancel. The add-new path lets you paste a different endpoint, go back, or cancel before AO sends the setup test. Before saving, AO also asks which notification priorities the webhook should receive: `urgent-only`, `urgent-action`, or `all`. For scriptable setup, pass `--routing-preset `. ## Use ```yaml title="agent-orchestrator.yaml" notifiers: webhook: plugin: webhook url: https://example.com/ao-events headers: Authorization: "Bearer ${MY_TOKEN}" X-Source: "agent-orchestrator" retries: 5 # default 3 retryDelayMs: 2000 # default 1000 notificationRouting: urgent: [webhook] action: [webhook] ``` ## Config | Key | Required | Default | What it does | | -------------- | -------- | ------- | ------------------------------------------------------------ | | `url` | โœ“ | โ€” | HTTPS endpoint that accepts POST | | `headers` | optional | `{}` | Custom request headers (supports `${ENV_VAR}` interpolation) | | `retries` | optional | `3` | Retry count on 429/5xx/network errors | | `retryDelayMs` | optional | `1000` | Base retry delay (exponential backoff) | ## Payload ### Envelope Every POST has a `type` discriminant. Most events use `notification`: ```json { "type": "notification", "event": { "id": "3f6a1b2c-...", "type": "pr.created", "priority": "info", "sessionId": "sess_01HXY...", "projectId": "myproject", "timestamp": "2026-04-17T15:00:00.000Z", "message": "PR opened: fix(auth): handle token expiry", "data": {} } } ``` When actions are attached (e.g. approve/retry buttons in supported notifiers): ```json { "type": "notification_with_actions", "event": { "...": "same fields as above" }, "actions": [{ "label": "View PR", "url": "https://github.com/owner/repo/pull/123" }] } ``` Free-form messages sent via `ao send` arrive as: ```json { "type": "message", "message": "Hello from AO", "context": {} } ``` ### Event types | `event.type` | Default priority | When emitted | | ---------------------------- | ---------------- | ------------------------------------------------- | | `session.spawned` | `info` | Session created | | `session.working` | `info` | Agent begins working on the issue | | `session.exited` | `info` | Agent process exited cleanly | | `session.killed` | `info` | Session was manually killed | | `session.idle` | `info` | Agent has been idle for a while | | `session.stuck` | `urgent` | Agent appears stuck (no activity) | | `session.needs_input` | `urgent` | Agent is blocked waiting for user input | | `session.errored` | `urgent` | Agent hit an unrecoverable error | | `pr.created` | `info` | PR opened by the agent | | `pr.updated` | `info` | PR updated (new commits pushed) | | `pr.merged` | `action` | PR was merged | | `pr.closed` | `info` | PR was closed without merging | | `ci.passing` | `info` | CI checks passed | | `ci.failing` | `warning` | CI checks failed | | `ci.fix_sent` | `info` | Agent sent a CI fix to itself | | `ci.fix_failed` | `warning` | Agent's CI fix attempt failed | | `review.pending` | `info` | PR review requested | | `review.approved` | `action` | PR approved by reviewer | | `review.changes_requested` | `warning` | Reviewer requested changes | | `review.comments_sent` | `info` | Review comments sent to agent | | `review.comments_unresolved` | `info` | Agent left unresolved review comments | | `automated_review.found` | `warning` | Automated review tool (e.g. BugBot) left comments | | `automated_review.fix_sent` | `info` | Agent sent fixes for automated review comments | | `merge.ready` | `action` | PR is approved + CI green โ€” ready to merge | | `merge.conflicts` | `warning` | Merge conflicts detected | | `merge.completed` | `action` | PR was successfully merged | | `reaction.triggered` | `info` | A configured reaction was triggered | | `reaction.escalated` | `urgent` | A reaction was escalated to a human notifier | | `summary.all_complete` | `info` | All sessions in the project completed | ### Signature verification The webhook notifier does **not** sign outgoing requests. There is no `X-Signature` or HMAC header added to POSTs. To secure your endpoint: - Use a secret `Authorization` header (set via `headers.Authorization` in config, passed as `${MY_TOKEN}`). - Restrict inbound traffic to your AO host via IP allowlist or VPN. - Use a reverse proxy with mutual TLS if needed. ## Why use this over a specific notifier - You want a single endpoint regardless of Slack/Discord/email routing (handled downstream). - You're building an internal AO dashboard of your own. - You want structured events for observability tooling (Datadog, Honeycomb, etc.).