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

# Event schemas

> Body shape per event_type for Aegis webhooks.  All deliveries are POST + HMAC-signed — see overview for headers and signature verification.

Each event delivery is a **POST** with a JSON body shaped per
`event_type`.  All bodies are wrapped in a stable envelope; the
event-specific fields live under `payload`.

## Envelope

```json theme={null}
{
  "schema_version": 1,
  "event_type":     "tx.policy.alert.triggered",
  "delivery_id":    "11111111-2222-3333-4444-555555555555",
  "attempt":        1,
  "occurred_at":    "2026-05-20T09:14:01.234Z",
  "payload":        { /* event-specific, see below */ }
}
```

Same envelope mirrored in headers:

| Header                | Mirror of     |
| --------------------- | ------------- |
| `X-Aegis-Event`       | `event_type`  |
| `X-Aegis-Delivery-Id` | `delivery_id` |
| `X-Aegis-Attempt`     | `attempt`     |

`delivery_id` is **stable across retries** — use it as your
idempotency key.

## `tx.policy.alert.triggered`

Fires from the KYT pipeline when `/check-transfer` (x402) or
`/v1/aegis/tx` (internal) finds at least one alert above your
configured `alert_min_level` (default `Medium`).

```json theme={null}
{
  "check_id":     "1c34db13-bb5a-4f1f-91b6-3c93b40c7f99",
  "tx_hash":      "0x52e8e15b…",
  "chain":        "ETH",
  "subgroup_id":  "00000000-…-system-default",
  "worst_level":  "Severe",
  "alerts": [
    {
      "category":       "Mixer",
      "riskLevel":      "Severe",
      "ruleType":       "Origin of funds / Indirect",
      "actualShare":    52.1,
      "thresholdShare": 1,
      "thresholdValue": 100,
      "entityName":     "Sender 0xab…ef01 (exposed to mixer)",
      "entityAddress":  "0xabcdef…",
      "direction":      "incoming",
      "entityScore":    0
    }
  ]
}
```

| Field         | Type   | Notes                                                                                                                                             |
| ------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `check_id`    | uuid   | Correlates to `tx_checks.id`; pass to `/check-transfer-result/{check_id}` for the full body                                                       |
| `tx_hash`     | string | 32-byte hex with `0x` prefix                                                                                                                      |
| `chain`       | string | `ETH`, `BSC`, `POLYGON`, `BASE`, `ARBITRUM`, `TRON`                                                                                               |
| `subgroup_id` | uuid   | The subgroup that owns the policy + alert config used                                                                                             |
| `worst_level` | string | `Low`, `Medium`, `High`, `Severe` — highest level across `alerts[]`                                                                               |
| `alerts`      | array  | Filtered subset above `alert_min_level`.  Each alert mirrors the policy-engine shape, see [tx-policy-alert detail](/webhooks/events#alert-fields) |

### Alert fields

| Field            | Type           | Notes                                                                                                                                                                         |
| ---------------- | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `category`       | string         | Display name of the BitOK risk category                                                                                                                                       |
| `riskLevel`      | string         | `Low`, `Medium`, `High`, `Severe`                                                                                                                                             |
| `ruleType`       | string         | One of `Direct exposure`, `Origin of funds / Indirect`, `Pattern`, etc.                                                                                                       |
| `actualShare`    | number         | % of USD-weighted composite that falls in this category                                                                                                                       |
| `thresholdShare` | number         | The rule's trigger threshold (% share)                                                                                                                                        |
| `thresholdValue` | number         | The rule's trigger threshold (absolute USD)                                                                                                                                   |
| `entityName`     | string \| null | Top-USD sender label; for indirect rules, prefixed `Sender ... (exposed to ...)`.  See [sender attribution semantics](/endpoints/check-transfer#sender-attribution-semantics) |
| `entityAddress`  | string \| null | Anchor sender address — lowercase EVM, base58 TRON                                                                                                                            |
| `direction`      | string         | Always `incoming` in V1 (we only analyse senders, not receivers)                                                                                                              |
| `entityScore`    | number         | 0 by default; reserved for future per-entity sub-scoring                                                                                                                      |

### Synthetic test payload

When fired by `POST /v1/webhooks/{id}/test`, the body has the same
shape with an additional top-level `synthetic: true` and sentinel
ids:

```json theme={null}
{
  "check_id":     "00000000-0000-0000-0000-000000000000",
  "tx_hash":      "0x0000000000000000000000000000000000000000000000000000000000000000",
  "chain":        "ETH",
  "subgroup_id":  "00000000-0000-0000-0000-000000000000",
  "worst_level":  "Severe",
  "alerts":       [{"category": "Synthetic Test", "riskLevel": "Severe", "ruleType": "Test", "actualShare": 100, "thresholdShare": 0, "direction": "incoming"}],
  "synthetic":    true
}
```

Receivers MAY short-circuit on `synthetic === true` to avoid
flooding case-review queues with test rows.

## `aegis.bfs.completed`

Fires when an async Tier-4 exposure job (`POST /explore`) completes
successfully.  Used for batch / pre-screening flows that can't tie
up a synchronous HTTP request.

```json theme={null}
{
  "job_id":   "9f0a3a07-37bb-4f99-91b5-…",
  "address":  "0xrecipient…",
  "network":  "ETH",
  "report_id": "f8e9d2…",
  "categories": [
    {"slug": "exchange", "severity": 10, "contribution": 84.3},
    {"slug": "mixer",    "severity": 100, "contribution":  3.1}
  ],
  "complete": true
}
```

| Field        | Type    | Notes                                                                             |
| ------------ | ------- | --------------------------------------------------------------------------------- |
| `job_id`     | uuid    | The `exposure_jobs.id` of the completed job                                       |
| `address`    | string  | Anchor address the BFS started from                                               |
| `network`    | string  | Same chain set as the rest of the API                                             |
| `report_id`  | uuid    | `exposure_reports.id` — fetch via `GET /explore/{job_id}/result` for the full row |
| `categories` | array   | USD-weighted contribution per slug, sorted by severity                            |
| `complete`   | boolean | `true` if BFS exhausted within budget, `false` if budget-capped                   |

## `aegis.bfs.failed`

Same shape minus `report_id` + `categories`, plus an `error` string.
Fires after the job exhausts its retry budget.

```json theme={null}
{
  "job_id":   "9f0a3a07-37bb-…",
  "address":  "0xrecipient…",
  "network":  "ETH",
  "error":    "etherscan rate-limit after 3 retries"
}
```

## `aegis.check.high_risk`

Fires from `/v1/aegis/check` when the verdict is `risk_level: high`
or `severe` (Tier 0/1/2/3/4 — any tier can trigger).

```json theme={null}
{
  "check_id":   "3b2c1d…",
  "address":    "0xrecipient…",
  "network":    "ETH",
  "risk_level": "high",
  "risk_score": 90,
  "tier_hit":   "tier_2",
  "categories": ["mixer", "darknet_market"]
}
```

| Field        | Type   | Notes                                                   |
| ------------ | ------ | ------------------------------------------------------- |
| `risk_level` | string | `high` or `severe` (this event won't fire below `high`) |
| `risk_score` | number | 0..100, inverted from category severity                 |
| `tier_hit`   | string | The first tier that escalated the verdict               |
| `categories` | array  | Slugs of contributing risk categories                   |
