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

# Manage subscriptions

> Register, list, update, delete, and test webhook subscriptions via the /v1/webhooks/* CRUD.

<Note>
  **Base path:** `https://api.aegis-kyt.com/v1/webhooks` · **Auth:** `X-API-Key: aeg_<48 chars>`
</Note>

## Register a subscription

```bash theme={null}
curl -X POST https://api.aegis-kyt.com/v1/webhooks \
  -H "X-API-Key: aeg_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "url":    "https://your-app.example.com/aegis-events",
    "events": ["tx.policy.alert.triggered"],
    "failure_threshold": 5
  }'
```

Response (HTTP 200):

```json theme={null}
{
  "schema_version": 1,
  "id":         "9f0a3a07-37bb-4f99-91b5-…",
  "url":        "https://your-app.example.com/aegis-events",
  "events":     ["tx.policy.alert.triggered"],
  "secret":     "aegws_secret_xxx…",
  "is_active":  true,
  "created_at": "2026-05-20T09:14:01.234Z"
}
```

<Warning>
  **`secret` is returned ONLY on creation.**  Store it server-side
  immediately — you'll need it to verify the `X-Aegis-Signature`
  header on every delivery, and we never return it again.  Lost the
  secret? Delete the subscription and create a new one.
</Warning>

Validation:

* `url` must start with `https://` (or `http://localhost` for dev).
* `events` must be a non-empty subset of the allowlist (see
  [Event schemas](/webhooks/events) for the current set).
* `failure_threshold` is optional, defaults to `5`, range `1..50`.
  Auto-disable kicks in after this many consecutive `gave_up`
  deliveries.

## List subscriptions

```bash theme={null}
curl https://api.aegis-kyt.com/v1/webhooks \
  -H "X-API-Key: aeg_YOUR_KEY_HERE"
```

```json theme={null}
{
  "schema_version": 1,
  "subscriptions": [
    {
      "id":                    "9f0a3a07-…",
      "url":                   "https://your-app.example.com/aegis-events",
      "events":                ["tx.policy.alert.triggered"],
      "is_active":             true,
      "created_at":            "2026-05-20T09:14:01.234Z",
      "updated_at":            "2026-05-20T09:14:01.234Z",
      "last_delivery_at":      null,
      "last_failure_at":       null,
      "consecutive_failures":  0,
      "failure_threshold":     5
    }
  ]
}
```

`secret` is **never** returned by this endpoint or by `GET /v1/webhooks/{id}`.

## Get one subscription

```bash theme={null}
curl https://api.aegis-kyt.com/v1/webhooks/9f0a3a07-… \
  -H "X-API-Key: aeg_YOUR_KEY_HERE"
```

Returns 404 if the subscription doesn't exist or isn't owned by
your API key (IDOR guard).

## Update — toggle / change events

```bash theme={null}
curl -X PUT https://api.aegis-kyt.com/v1/webhooks/9f0a3a07-… \
  -H "X-API-Key: aeg_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{
    "is_active": true,
    "events":    ["tx.policy.alert.triggered", "aegis.check.high_risk"]
  }'
```

Setting `is_active: true` also resets `consecutive_failures` to
zero, so this is the canonical way to re-enable a subscription
after auto-disable.

Both fields are optional — send only what you want to change.

## Delete

```bash theme={null}
curl -X DELETE https://api.aegis-kyt.com/v1/webhooks/9f0a3a07-… \
  -H "X-API-Key: aeg_YOUR_KEY_HERE"
```

Cascades to the `aegis_webhook_deliveries` history; you can't get
this data back.  Pending deliveries already queued in
BackgroundTasks may still fire once after delete (best-effort).

## Send a synthetic test event

```bash theme={null}
curl -X POST https://api.aegis-kyt.com/v1/webhooks/9f0a3a07-…/test \
  -H "X-API-Key: aeg_YOUR_KEY_HERE" \
  -H "Content-Type: application/json" \
  -d '{"event_type": "tx.policy.alert.triggered"}'
```

Response:

```json theme={null}
{
  "schema_version":       1,
  "subscription_id":      "9f0a3a07-…",
  "event_type":           "tx.policy.alert.triggered",
  "deliveries_enqueued":  1,
  "synthetic":            true
}
```

What happens next:

1. We build a synthetic payload for the requested `event_type` —
   sentinel ids (`00000000-…`), a top-level `synthetic: true`
   field, and minimum-viable fields per event\_type.
2. We sign + POST it to **just this subscription's URL** (the
   `only_subscription_id` filter — your other subs won't see it).
3. Retry policy is the **same as production events**: 5 attempts
   over \~1 h.  This intentionally lets you verify your retry /
   idempotency handling, not just first-call signature checks.

### Request body

| Field        | Required | Default                                      | Notes                                                                                                 |
| ------------ | -------- | -------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `event_type` | no       | First event in the subscription's `events[]` | Must be one of the events you're subscribed to — sending a test for an unsubscribed event returns 400 |

### Errors

| Status | Code          | Meaning                                           |
| ------ | ------------- | ------------------------------------------------- |
| 400    | `bad_request` | `event_type` is not in this subscription's events |
| 400    | `bad_request` | Subscription has no events configured             |
| 401    | (auth)        | Missing or invalid API key                        |
| 404    | `not_found`   | Subscription not owned by this caller             |

## Recipes

### Verify your handler before going live

```bash theme={null}
# 1) Create the subscription pointed at your prod endpoint
SUB=$(curl -s -X POST https://api.aegis-kyt.com/v1/webhooks \
  -H "X-API-Key: $AEG" -H "Content-Type: application/json" \
  -d '{"url": "https://prod.example.com/aegis", "events": ["tx.policy.alert.triggered"]}')
SUB_ID=$(echo "$SUB" | jq -r .id)

# 2) Save the secret — last chance!
echo "$SUB" | jq -r .secret > /run/secrets/aegis_webhook_secret

# 3) Fire a synthetic event — your endpoint should accept + return 2xx
curl -X POST https://api.aegis-kyt.com/v1/webhooks/$SUB_ID/test \
  -H "X-API-Key: $AEG"

# 4) If the test fails, check logs.  Common causes:
#    - signature mismatch (re-serialised JSON before HMAC, wrong secret)
#    - your endpoint rejects {synthetic: true} payloads
#    - your firewall blocks the source IP (our outbound is from the
#      Hetzner range; whitelist if you have egress filtering)
```

### Re-enable after auto-disable

```bash theme={null}
curl -X PUT https://api.aegis-kyt.com/v1/webhooks/$SUB_ID \
  -H "X-API-Key: $AEG" -H "Content-Type: application/json" \
  -d '{"is_active": true}'
```

(also resets `consecutive_failures` to 0)
