> ## Documentation Index
> Fetch the complete documentation index at: https://docs.komodor.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Outbound webhooks

> Have KAOP post signed, retried, auditable events to your own endpoints so external systems react to what your agents do.

Agents produce facts your other systems need — a run finished, a deployment failed, an incident was
triaged. An **outbound webhook** is the Komodor Agentic Operation Platform (KAOP) posting those
events to an endpoint you own, signed and retried, with a durable record of every attempt. This page
covers how to set one up, what the request looks like, and how delivery behaves when your endpoint
does not.

## Outbound is not inbound

Two objects, opposite directions, both under **Integrations**:

|            | **Endpoints (inbound)**                                         | **Webhooks (outbound)**                  |
| ---------- | --------------------------------------------------------------- | ---------------------------------------- |
| Direction  | Your systems post to KAOP                                       | KAOP posts to your systems               |
| Purpose    | Start a run or a workflow from an external event                | React to something an agent did          |
| You supply | Nothing — KAOP gives you a URL and a token                      | The destination URL and a signing secret |
| Covered in | [Integrations](/manage-your-agents/build/integrations-overview) | This page                                |

If your goal is "an alert should start an investigation", you want an inbound endpoint. If your goal
is "our ticketing system should know when the investigation is done", you want an outbound webhook.

## Setting one up

Outbound webhooks live under **Integrations → Webhooks (outbound)**. **New notification sink** opens
a four-step wizard.

<Steps>
  <Step title="Destination">
    Give it a name, then choose where events go. **Webhook** sends signed POST requests to any HTTPS
    endpoint — you supply the destination URL and a signing secret. **Slack** posts messages to a
    channel instead, using your existing Slack connection.
  </Step>

  <Step title="Events">
    Pick which event types this sink should receive. The catalog is the same regardless of which
    sources feed it.
  </Step>

  <Step title="Payload & test">
    Inspect exactly what will be sent — always JSON — and use **Send test event** to fire a real
    request at your endpoint before anything depends on it.
  </Step>

  <Step title="Subscriptions">
    Choose which sources feed this sink: specific agents, specific workflows, specific incident
    pipelines, or the whole workspace.
  </Step>
</Steps>

A sink can also be picked while you are creating an agent, from the Triggers step's **When the run
finishes** section — the same object, reached from where you were already working.

## What you can subscribe to

Three families of event, in one catalog:

| Group                  | Events                                                                                                               |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------- |
| **Agent lifecycle**    | `agent.created`, `agent.updated`, `agent.archived`, `deployment.succeeded`, `deployment.failed`, `agent.unreachable` |
| **Run lifecycle**      | `run.finished` (any terminal status), `run.succeeded`, `run.failed`, `run.cancelled`, `run.started`                  |
| **Incident lifecycle** | `incident.triaged` (investigation complete), `incident.errored`, `incident.resolved`                                 |

Events and sources are separate axes, which is what makes fan-in useful. One sink can take
`run.failed` from a single agent; another can take `run.finished` from every source in the workspace;
a third can take incident events from two specific incident pipelines. `agent.created` is
workspace-scoped by nature — there is no per-agent source for an agent that does not exist yet.

## What gets delivered

One fixed JSON envelope, versioned, with no templating — so there is exactly one shape to write a
receiver against:

```json theme={null}
{
  "event": "run.finished",
  "status": "failed",
  "source": { "type": "agent", "id": "agent-id" },
  "run_id": "run_abc123",
  "duration_ms": 1234,
  "run_url": "https://app.example.com/runs/run_abc123"
}
```

The one thing you control about the shape is **Include run output**, which is off by default because
a run's output can be large or sensitive. The toggle is read at send time, so flipping it takes effect
on the very next delivery — you do not re-subscribe.

Payloads are capped. When an envelope would exceed the cap, the output field is replaced with a
truncation marker rather than being cut mid-structure, so your receiver always parses valid JSON and
can tell that something was withheld.

### The request

| Property                                   | Value                                                                                    |
| ------------------------------------------ | ---------------------------------------------------------------------------------------- |
| Method                                     | `POST`                                                                                   |
| `Content-Type`                             | `application/json`                                                                       |
| `X-AgentOps-Signature`                     | `sha256=<hex>` — HMAC-SHA256 of the raw request body, keyed by the sink's signing secret |
| `X-AgentOps-Notification-Envelope-Version` | The envelope version this payload was rendered at                                        |

Verify the signature over the **raw bytes** you received, before parsing. Re-serializing the JSON and
signing that will not match.

Two things KAOP will not do, both deliberate:

* **Redirects are never followed.** Any `3xx` response is treated as a failed attempt, because a
  redirect to an internal address is a way around the destination you configured.
* **Private and loopback destinations are refused** — and the destination is re-resolved and
  re-checked on every send, not only when the sink was created, so a name that starts resolving
  internally later is caught at delivery time.

### Rotating the signing secret

**Rotate signing secret** issues a new one and shows it to you once. Rotation is an audited,
elevated action, so plan for the cutover: your receiver should be ready to accept the new secret
before you rotate.

## How delivery behaves

| Outcome        | Meaning                                                            |
| -------------- | ------------------------------------------------------------------ |
| `pending`      | The attempt was recorded and the request is in flight              |
| `success`      | Your endpoint returned a success status                            |
| `failed`       | A connection error, a timeout, a redirect, or a non-success status |
| `rate_limited` | Your endpoint returned `429` or `503`                              |
| `exhausted`    | Every retry was spent with no success                              |

A failed attempt is retried with backoff, up to a bounded number of attempts, and then dead-lettered
— which marks the delivery `exhausted` in the log. There is no automatic follow-up after that, so
`exhausted` is the state to alert on if these events are load-bearing for you.

Rate limiting is handled differently from failure, and in your favour: a `429` or `503` is retried
after the delay your `Retry-After` header asks for, and that wait does not consume the retry budget.
So back-pressure from your side costs you a delay rather than a dropped event.

A disabled or deleted sink is a silent no-op — events for it are dropped rather than queued up for a
destination that no longer wants them.

## Auditing what was sent

Every sink row carries a **History** link. It opens the delivery log for that sink: when each attempt
happened, which event it carried, which attempt number it was, the result, and the response body your
endpoint returned (captured up to a few kilobytes — enough for the diagnostic, which is almost always
at the front). The log is durable and per-workspace, so "did this fire, and what came back?" has an
answer well after the fact.

Configuration and deliveries are recorded in two different places on purpose:

* **Configuration changes are audited** — creating, updating, or deleting a sink, rotating its
  secret, and subscribing or unsubscribing a source all write audit entries.
* **Deliveries are not audit entries.** They are operational data, and they live in the delivery log
  above.

## Who can manage them

Reading the list of sinks is a workspace-scoped permission. Creating, updating, deleting, rotating a
secret, and sending a test event are **administrative** actions held at a deliberately higher bar —
an outbound webhook is a workspace-chosen arbitrary URL, which puts it in the same class as managing
credentials. See [Roles & permissions](/security-and-governance/identity-and-access/roles-permissions).

## Next steps

<CardGroup cols={2}>
  <Card title="Runs & evidence" href="/manage-your-agents/run/runs-evidence">
    What the `run.*` events are describing.
  </Card>

  <Card title="Integrations" href="/manage-your-agents/build/integrations-overview">
    Inbound endpoints, and the connection catalog.
  </Card>

  <Card title="Audit log" href="/security-and-governance/controls/audit-log">
    Where configuration changes to a sink are recorded.
  </Card>

  <Card title="Channels" href="/manage-your-agents/run/channels">
    The conversational route into and out of Slack.
  </Card>
</CardGroup>


## Related topics

- [Integrations overview](/manage-your-agents/build/integrations-overview.md)
- [Triggers & schedules](/manage-your-agents/build/triggers-schedules.md)
- [Runs & evidence](/manage-your-agents/run/runs-evidence.md)
- [How Run works](/manage-your-agents/run/how-it-works.md)
- [Built-in integrations](/manage-your-agents/build/built-in-integrations.md)
