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

# Events and webhooks

> Two ways to hear that the mind changed: a live stream, and signed outbound deliveries.

## What this family does

`GET /v1/events` is an organization-scoped Server-Sent Events stream for a live view. The six webhook routes register endpoints that receive the same catalogue as signed HTTP deliveries, with retries and a delivery log you can inspect.

The two transports carry the same event catalogue and apply the same reach rules, so a receiver sees the same set either way. The difference is durability: the stream does not replay, and webhooks retry.

## Routes

| Method   | Path                                                                   | Scope   | What it does                            |
| -------- | ---------------------------------------------------------------------- | ------- | --------------------------------------- |
| `GET`    | [`/v1/events`](/api-reference/events/stream-events)                    | `read`  | Stream organization events (SSE)        |
| `GET`    | [`/v1/webhooks`](/api-reference/events/list-webhooks)                  | `read`  | List webhook endpoints                  |
| `POST`   | [`/v1/webhooks`](/api-reference/events/create-webhook)                 | `write` | Register a webhook endpoint             |
| `DELETE` | [`/v1/webhooks/{webhook_id}`](/api-reference/events/delete-webhook)    | `write` | Delete a webhook endpoint               |
| `GET`    | [`/v1/webhooks/{webhook_id}`](/api-reference/events/get-webhook)       | `read`  | Inspect a webhook endpoint              |
| `PATCH`  | [`/v1/webhooks/{webhook_id}`](/api-reference/events/update-webhook)    | `write` | Update a webhook endpoint               |
| `POST`   | [`/v1/webhooks/{webhook_id}/test`](/api-reference/events/test-webhook) | `write` | Send a test event to a webhook endpoint |

## Headers that apply here

Neither header is declared as an OpenAPI parameter, so neither appears in the generated schema tables.

* **`Idempotency-Key`** makes a retry safe on `POST /v1/webhooks`. See [Idempotency](/platform/idempotency).

## The shape of a call

Register a webhook endpoint.

<CodeGroup>
  ```bash cURL theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  curl -X POST https://api.get-exo.com/v1/webhooks \
    -H "X-Exo-API-Key: $EXO_KEY" \
    -H "Content-Type: application/json" \
    -d '{"url": "https://hooks.example.com/exo", "eventTypes": ["contradiction.detected"]}'
  ```

  ```python Python theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  import httpx

  response = httpx.post(
      "https://api.get-exo.com/v1/webhooks",
      headers={"X-Exo-API-Key": EXO_KEY},
      json={
          "url": "https://hooks.example.com/exo",
          "eventTypes": ["contradiction.detected"],
      },
  )
  secret = response.json()["secret"]  # shown once
  ```

  ```typescript TypeScript theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
  const response = await fetch("https://api.get-exo.com/v1/webhooks", {
    method: "POST",
    headers: {
      "X-Exo-API-Key": process.env.EXO_KEY!,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      url: "https://hooks.example.com/exo",
      eventTypes: ["contradiction.detected"],
    }),
  });
  const { secret } = await response.json(); // shown once
  ```
</CodeGroup>

The response:

```json theme={"theme":{"light":"github-light-default","dark":"github-dark-default"}}
{
  "id": "3b0f1c2e-6a44-4c1f-9f2b-0d5a7c8e1042",
  "url": "https://hooks.example.com/exo",
  "eventTypes": ["contradiction.detected"],
  "inactiveEventTypes": [],
  "disabled": false,
  "disabledReason": null,
  "consecutiveFailures": 0,
  "lastDeliveryAt": null,
  "lastSuccessAt": null,
  "createdAt": "2026-07-14T09:12:03Z",
  "updatedAt": null,
  "secret": "whsec_5f2a9c1e7b40d83a6cf1e29b47d05a8c3e6f1b24d97"
}
```

## Traps

**The stream does not replay.** `Last-Event-ID` is accepted so resuming clients do not error, and it is ignored. A client that must not miss a transition should use a webhook.

**Five concurrent streams per organization.** The sixth is 429 `too_many_streams` with `Retry-After`.

**The signing secret is shown once.** It is in the registration response and nowhere else. A replayed `Idempotency-Key` returns it redacted, not in the clear.

**Endpoints auto-disable after 8 consecutive failures.** Re-enable with `PATCH {"disabled": false}`, which also clears the failure streak.

**Payloads are thin by design.** A delivery carries identifiers and small scalars, never node content. Read the object back through the API, so your receiver sees exactly what your credentials allow.

## Related

<Columns cols={2}>
  <Card title="API reference" icon="book-open" href="/api-reference/overview">
    Base URL, authentication, and the conventions every route shares.
  </Card>

  <Card title="Errors" icon="circle-alert" href="/platform/errors">
    The problem envelope and the full code catalogue.
  </Card>
</Columns>
