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

# Webhook events

> Know exactly when each run event fires and what its body contains.

An endpoint receives one of four events. The webhook event types are a fixed set, defined in code, and every one is about a run. Each has the same envelope: `id`, `type`, `webhook_version`, `created_at`, and `data.run`. This page shows when each event fires and a full `full`-mode body for each. Registration, headers, signatures, and retries are on [Webhooks](/api-reference/webhooks/overview). The schema is `WebhookEvent` in the [OpenAPI document](/openapi.yaml).

| Event            | Fires when                                                                                                  | `data.run.status`         | Carries `output`                                                                     |
| ---------------- | ----------------------------------------------------------------------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------ |
| `run.completed`  | The run reached `completed` or `degraded`                                                                   | `completed` or `degraded` | Yes                                                                                  |
| `run.failed`     | The run reached `failed`                                                                                    | `failed`                  | No                                                                                   |
| `run.cancelled`  | The run reached `cancelled`                                                                                 | `cancelled`               | No                                                                                   |
| `run.superseded` | Nexio attached a corrected result to a run, and the engine's configuration sets `notify_on_supersede: true` | The run's status          | Depends on your organization's delivery setup; see [run.superseded](#run-superseded) |

Rules that hold for every event:

* `data.run` is built from the stored run with the same status fields `GET /api/v1/runs/{run_id}` returns, but it is not the same response. It never carries `output_phase`, `completed_deterministic_at`, `warnings`, `work_items`, `input`, `computed_at_head`, `served_head` or `stale`; those come only from `GET`. `output` is the stored output: it is not trimmed to the operator summary as `GET` trims a `matching` run's output, and no seat's field policy is applied to it. Read the run with `GET` when you need what a person would see. Its fields are defined on [Runs](/engines/runs).
* `output` and `solutions` appear only on completed and degraded runs. `solutions` appears only for engines that produce ranked solutions. `output` has the shape of the engine's own output contract.
* The event `id` is a UUID derived from the event type and the run ID, so a retried or resent delivery carries the same `id`. In one delivery setup, a `run.superseded` `id` is derived from the correction instead; see [run.superseded](#run-superseded).
* In `thin` mode, `data.run` carries only `run_id`, `status`, `environment`, `attempt`, `created_at`, `completed_at`, and `error`. See [Payload modes](/api-reference/webhooks/overview#payload-modes).

The examples use the contract fixture engine that Nexio publishes for testing (an `entity_analysis` engine). The `run.completed`, `run.failed` and `run.superseded` examples are sandbox runs with a forced `test_scenario` (see [Sandbox fixtures](/reference/sandbox-fixtures)), so `environment` is `test`. Your engine's `output` has its own shape, and `stage` is a progress hint whose values differ by engine.

## run.completed

Fires once, when the run reaches `completed` or `degraded`. There is no separate degraded event. A degraded run finished and returned an output that is incomplete or of reduced quality. What makes a run degraded depends on the engine type; see [Runs](/engines/runs#degraded-runs). For example, an `entity_analysis` run whose enrichment source (such as FEMA flood data) timed out stays `completed`, with the gap recorded in the output's diagnostics. Check `data.run.status` and the output's diagnostics before you treat a result as clean.

```json theme={null}
{
  "id": "c4a1e9d2-7b3f-5e60-8a14-2d9f6b0c3e71",
  "type": "run.completed",
  "webhook_version": "2026-03-22",
  "created_at": "2026-09-23T14:05:32Z",
  "data": {
    "run": {
      "run_id": "8b3e6f1a-2c47-4d9e-a5b0-7f1c3d9e2a64",
      "engine_type": "entity_analysis",
      "engine_version": "1.0",
      "engine_config_version_hash": "f96a86a609a2b1ea",
      "status": "completed",
      "environment": "test",
      "stage": "COMPLETE",
      "output": {
        "diagnostics": [],
        "enrichment": {
          "fema_nfhl": {
            "attempted": true,
            "fetched_at": "2026-07-09T15:04:05Z",
            "kind": "fema_nfhl",
            "payload": {
              "base_flood_elevation_feet": null,
              "flood_zone": "AE",
              "flood_zone_confidence": "rooftop",
              "flood_zone_description": "High flood risk",
              "flood_zone_subtype": "",
              "matched_address": "100 Main St, Example, NC 28202",
              "matched_feature_count": 1,
              "sfha": null
            },
            "status": "succeeded"
          }
        },
        "fixture_contract": {
          "decision": "accepted"
        },
        "gaps": [],
        "profile_summary": {},
        "response_type": "GENERIC_FIXTURE_ANALYSIS",
        "summary": {
          "high_severity": 0,
          "low_severity": 0,
          "medium_severity": 0,
          "total_gaps": 0
        }
      },
      "duration_ms": 28410,
      "total_duration_ms": 29000,
      "attempt": 1,
      "trace_id": "9c900cbde9ec92b1953f4b42858a8710",
      "created_at": "2026-09-23T14:05:02Z",
      "started_at": "2026-09-23T14:05:03Z",
      "completed_at": "2026-09-23T14:05:31Z"
    }
  }
}
```

The same event for a degraded run differs in `status` and in the output the engine returned. This example is the fixture engine's forced `degraded` result (`"test_scenario": "degraded"`). Its output carries only an enrichment timeout, which in a live `entity_analysis` run would leave the run `completed`.

```json theme={null}
{
  "id": "0e8f5b21-6c9a-5d47-b3e2-8a1f4c7d9b06",
  "type": "run.completed",
  "webhook_version": "2026-03-22",
  "created_at": "2026-09-23T14:11:09Z",
  "data": {
    "run": {
      "run_id": "3d7a9c15-e2b4-4f68-8d01-6b5e2c9f7a43",
      "engine_type": "entity_analysis",
      "engine_version": "1.0",
      "engine_config_version_hash": "f96a86a609a2b1ea",
      "status": "degraded",
      "environment": "test",
      "stage": "COMPLETE",
      "output": {
        "diagnostics": [
          {
            "code": "ENRICHMENT_DEGRADED",
            "details": {
              "error_class": "timeout",
              "upstream": "nfhl"
            },
            "message": "",
            "severity": "warn",
            "source": "fema_nfhl"
          }
        ],
        "enrichment": {
          "fema_nfhl": {
            "attempted": true,
            "diagnostics": [
              {
                "code": "ENRICHMENT_DEGRADED",
                "details": {
                  "error_class": "timeout",
                  "upstream": "nfhl"
                },
                "message": "",
                "severity": "warn",
                "source": "fema_nfhl"
              }
            ],
            "kind": "fema_nfhl",
            "status": "unavailable"
          }
        },
        "fixture_contract": {
          "decision": "review"
        },
        "gaps": [],
        "profile_summary": {},
        "response_type": "GENERIC_FIXTURE_ANALYSIS",
        "summary": {
          "high_severity": 0,
          "low_severity": 0,
          "medium_severity": 0,
          "total_gaps": 0
        }
      },
      "duration_ms": 41870,
      "total_duration_ms": 42000,
      "attempt": 1,
      "trace_id": "4be1f07a93c2d85e61a0b7c4d9e3f218",
      "created_at": "2026-09-23T14:10:26Z",
      "started_at": "2026-09-23T14:10:27Z",
      "completed_at": "2026-09-23T14:11:08Z"
    }
  }
}
```

Webhook deliveries never carry a `warnings` array. If the engine's saved configuration turns on `expose_warnings` (off by default), read the run with `GET /api/v1/runs/{run_id}` to get it. The setting is read on each request (see [Configuration](/engines/configuration#settings-read-from-the-saved-configuration)).

## run.failed

Fires once, when the run reaches `failed`. There is no `output`. `error` is a readable message and `error_details` is structured detail. `attempt` is the attempt on which the run failed; a value above 1 means Nexio retried the run before it gave up. The example is the fixture engine's forced `failed` result (`"test_scenario": "failed"`); a live failure carries the engine's own `error` and `error_details`.

```json theme={null}
{
  "id": "7a2d4f98-1e3b-5c06-9f7a-2b8e5d1c4a39",
  "type": "run.failed",
  "webhook_version": "2026-03-22",
  "created_at": "2026-09-23T14:20:45Z",
  "data": {
    "run": {
      "run_id": "f1c86e3b-9a27-4d5f-b04e-2c7a9e1d6b58",
      "engine_type": "entity_analysis",
      "engine_version": "1.0",
      "engine_config_version_hash": "f96a86a609a2b1ea",
      "status": "failed",
      "environment": "test",
      "stage": "FAILED",
      "duration_ms": 3120,
      "total_duration_ms": 44000,
      "attempt": 1,
      "error": "fixture provider failed",
      "error_details": {
        "code": "FIXTURE_PROVIDER_FAILURE"
      },
      "trace_id": "d2a8e4b61f7c93054e1b8a6c2d9f7e30",
      "created_at": "2026-09-23T14:20:00Z",
      "started_at": "2026-09-23T14:20:40Z",
      "completed_at": "2026-09-23T14:20:44Z"
    }
  }
}
```

## run.cancelled

Fires once, when the run reaches `cancelled`. A run is cancelled when someone asks for it with `POST /api/v1/runs/{run_id}/cancel`. A queued run is cancelled at once; a running run stops at its next safe point. There is no `output`. See [Runs](/engines/runs) for cancellation.

```json theme={null}
{
  "id": "2b9e6c14-8d3f-5a71-a6c0-4e2f8b7d1c95",
  "type": "run.cancelled",
  "webhook_version": "2026-03-22",
  "created_at": "2026-09-23T14:31:18Z",
  "data": {
    "run": {
      "run_id": "6e4b2a97-c1d8-4f3e-9b75-8a2c6d0e4f11",
      "engine_type": "entity_analysis",
      "engine_version": "1.0",
      "engine_config_version_hash": "f96a86a609a2b1ea",
      "status": "cancelled",
      "environment": "live",
      "stage": "cancelled",
      "total_duration_ms": 16000,
      "attempt": 1,
      "trace_id": "71c3e9a0b5d2f84e6a1c7b3d9e0f2a58",
      "created_at": "2026-09-23T14:31:01Z",
      "started_at": "2026-09-23T14:31:02Z",
      "completed_at": "2026-09-23T14:31:17Z"
    }
  }
}
```

## run.superseded

Fires when Nexio attaches a corrected result to a run. Nexio keeps the original result. Corrections are made by Nexio; there is no public route to create one. See [Defensibility](/engines/defensibility).

Two conditions must both hold:

1. The engine's configuration sets `notify_on_supersede: true`. It is `false` by default. Which configuration Nexio reads depends on your organization's delivery setup (see the table below and [Configuration](/engines/configuration#settings-read-from-the-saved-configuration)).
2. The endpoint lists `run.superseded` in `events`.

Nexio sets your organization's delivery setup. A new organization starts in the per-run setup. Ask Nexio which one yours uses.

|                             | Per-correction setup                                                                             | Per-run setup                                                                                                                                    |
| --------------------------- | ------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| Configuration Nexio reads   | The configuration the run executed, so `notify_on_supersede` must be in the version the run used | The engine's saved configuration when the correction is recorded                                                                                 |
| Event `id`                  | Derived from the correction, so each correction of a run is a new event                          | Derived from the event type and the run ID, so a later correction of the same run sends no new delivery to an endpoint that already received one |
| `data.run` in a `full` body | The corrected output, when the run is `completed` or `degraded`, and no `solutions`              | The run's stored (original) output and its `solutions`, as on `run.completed`                                                                    |

In both setups `GET /api/v1/runs/{run_id}` keeps returning the original output, and a `thin` endpoint receives no output. In the per-correction setup the corrected output reaches you only in a `full` `run.superseded` delivery. In the per-run setup the delivery tells you that a correction exists but does not carry it.

The example is a `full` body in the per-correction setup, correcting the `run.completed` example above.

```json theme={null}
{
  "id": "9f3c7e20-4a6b-5d18-8e92-1b7d3f5a0c64",
  "type": "run.superseded",
  "webhook_version": "2026-03-22",
  "created_at": "2026-09-24T09:12:40Z",
  "data": {
    "run": {
      "run_id": "8b3e6f1a-2c47-4d9e-a5b0-7f1c3d9e2a64",
      "engine_type": "entity_analysis",
      "engine_version": "1.0",
      "engine_config_version_hash": "f96a86a609a2b1ea",
      "status": "completed",
      "environment": "test",
      "stage": "COMPLETE",
      "output": {
        "diagnostics": [],
        "enrichment": {
          "fema_nfhl": {
            "attempted": true,
            "fetched_at": "2026-07-09T15:04:05Z",
            "kind": "fema_nfhl",
            "payload": {
              "base_flood_elevation_feet": null,
              "flood_zone": "X",
              "flood_zone_confidence": "rooftop",
              "flood_zone_description": "Minimal flood risk",
              "flood_zone_subtype": "",
              "matched_address": "100 Main St, Example, NC 28202",
              "matched_feature_count": 1,
              "sfha": null
            },
            "status": "succeeded"
          }
        },
        "fixture_contract": {
          "decision": "accepted"
        },
        "gaps": [],
        "profile_summary": {},
        "response_type": "GENERIC_FIXTURE_ANALYSIS",
        "summary": {
          "high_severity": 0,
          "low_severity": 0,
          "medium_severity": 0,
          "total_gaps": 0
        }
      },
      "duration_ms": 28410,
      "total_duration_ms": 29000,
      "attempt": 1,
      "trace_id": "9c900cbde9ec92b1953f4b42858a8710",
      "created_at": "2026-09-23T14:05:02Z",
      "started_at": "2026-09-23T14:05:03Z",
      "completed_at": "2026-09-23T14:05:31Z"
    }
  }
}
```

Because deliveries are unordered, a `run.superseded` delivery can reach you before the `run.completed` for the same run. Key your stored result on `run_id`. In the per-correction setup, let the corrected output win over the original, whatever order they arrive in.

## Events that do not exist

* There is no `run.degraded` event. Degraded runs arrive as `run.completed`.
* There are no events for runs that are queued or processing. Poll the run for progress.
* Platform event types such as `dataset.refreshed` are not webhook events and cannot be listed in an endpoint's `events`. Nexio can deliver them to your endpoint through an automation; see [Platform event deliveries](/api-reference/webhooks/overview#platform-event-deliveries-to-the-same-endpoint).

<CardGroup cols={2}>
  <Card title="Webhooks" href="/api-reference/webhooks/overview">
    Register, verify, retry, resend.
  </Card>

  <Card title="Runs" href="/engines/runs">
    Every field of the run object.
  </Card>
</CardGroup>
