---
title: Webhook notifier
description: Generic HTTP POST. Retries, exponential backoff, custom headers.
---

<div style={{ display: "flex", alignItems: "center", gap: "0.75rem", margin: "0.5rem 0 1.25rem" }}>
  <Logo name="webhook" size={28} />
  <span style={{ fontSize: "0.8125rem", color: "var(--color-fd-muted-foreground)" }}>Slot: <code>notifier</code> · Name: <code>webhook</code></span>
</div>

<PlatformSupport macos="full" linux="full" windows="full" />

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.

## Use

```yaml title="agent-orchestrator.yaml"
notifier:
  - type: webhook
    url: https://example.com/ao-events
    headers:
      Authorization: "Bearer ${MY_TOKEN}"
      X-Source: "agent-orchestrator"
    retries: 5                  # default 3
    retryDelayMs: 2000          # default 1000
```

## 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.).
