# Webhooks

Source: https://docs.lookover.ai/webagent/reference/webhooks

> Get a signed HTTPS call when a run or a workflow run ends. The envelope, how to verify it, and the payload of workflow_run.completed and workflow_run.failed.

A webhook endpoint is a public HTTPS URL that webagent calls with one JSON body when something happens. Add endpoints in the dashboard under **Settings**, then **Webhooks**, or with `POST /v1/settings/webhooks` (admin role). Pick the events each endpoint receives. The signing secret is shown once, when you create the endpoint. Send a `test.ping` with `POST /v1/settings/webhooks/:id/test` (admin role). Recent deliveries are at `GET /v1/settings/webhooks/:id/deliveries`.

The URL must be public: the same guard as for the cloud browser applies, so a private or loopback address is refused. A Slack incoming-webhook URL gets `{ "text": "…" }` instead of the envelope below.

## The envelope [#the-envelope]

Every event has the same outer shape:

| Field | Meaning |
|---|---|
| `id` | The id of the event. It stays the same when a failed delivery is retried, so you can drop duplicates |
| `type` | The event name |
| `created_at` | ISO 8601 time |
| `data` | The payload of that event |

| Event | Sent when | `data` |
|---|---|---|
| `run.started` | A skill run starts | `runId`, `skill`, `version`, `sessionId` |
| `run.completed` | A skill run ends with `passed` | `runId`, `skill`, `status`, `summary` and `reportUrl` (both optional) |
| `run.failed` | A skill run ends with `failed`, `error`, `cancelled` or `denied` | The same fields as `run.completed` |
| `workflow_run.completed` | A workflow run settles with a verdict that is not `fail` or `error` | [Workflow run payload](#workflow-run-events) |
| `workflow_run.failed` | A workflow run settles with the verdict `fail` or `error` | [Workflow run payload](#workflow-run-events) |
| `test.ping` | You send a test. It always goes through | `message` |

## Verify the signature [#verify-the-signature]

Every call has the header `webagent-signature: t=<unix seconds>,v1=<hex>`. `v1` is the HMAC-SHA256, with your endpoint secret as the key, of the text `<t>.<raw body>`. Compute it over the raw body, before you parse it. Reject a call with a wrong signature or an old `t`. Roll the secret with `POST /v1/settings/webhooks/:id/roll-secret`.

Answer with a `2xx` status. A failed delivery is retried with growing pauses for about a day, then given up. The deliveries list shows each attempt.

## Workflow run events [#workflow-run-events]

A workflow run is one run of your tests, from the dashboard, the API, MCP, a schedule or [CI](/guides/qa-in-ci). When it settles, one event is sent. `workflow_run.failed` is for the verdicts `fail` and `error`. `workflow_run.completed` is for every other settled run, and `data.verdict` says which. `error` means the test could not run to a result. It is not an app failure, so read `data.verdict` and `data.counts` before you open a bug.

```json title="workflow_run.failed"
{
  "id": "<event id>",
  "type": "workflow_run.failed",
  "created_at": "2026-11-13T02:14:01.000Z",
  "data": {
    "runId": "wfr_us_xxxxxxxxxxxxxxxxxxxxxxxx",
    "workflowId": "wf_us_xxxxxxxxxxxxxxxxxxxxxxxx",
    "workflowKey": "checkout-flow",
    "workflowVersion": 3,
    "verdict": "fail",
    "trigger": "ci",
    "counts": { "cases": 2, "passed": 1, "failed": 1, "errored": 0, "running": 0 },
    "ci": { "provider": "github", "repo": "acme/web", "pr": 12 },
    "costMicros": 1200,
    "startedAt": 1794536000000,
    "endedAt": 1794536041000,
    "durationMs": 41000,
    "dashboardUrl": "https://webagent.lookover.ai/workflows/runs/wfr_us_xxxxxxxxxxxxxxxxxxxxxxxx"
  }
}
```

| Field | Meaning |
|---|---|
| `runId` | The workflow run id (`wfr_…`) |
| `workflowId`, `workflowKey`, `workflowVersion` | The saved workflow the run belongs to. `null` for an ad hoc run with no saved workflow, and `workflowKey` is `null` when the workflow has no key |
| `verdict` | `pass`, `fail`, `error` or `running`. A settled run is not `running` |
| `trigger` | `manual`, `api`, `mcp`, `ci` or `schedule` |
| `counts` | `cases`, `passed`, `failed`, `errored` and `running`, and when set `flaky` and `optionalFailed` |
| `ci` | Where a CI run came from: `provider` (`github` or `other`) and, when known, `repo`, `ref`, `sha`, `pr` and `buildUrl`. `null` when the run did not come from CI |
| `costMicros` | The cost in millionths of a US dollar |
| `startedAt`, `endedAt` | Unix time in milliseconds |
| `durationMs` | How long the run took, in milliseconds |
| `dashboardUrl` | The run page in the dashboard. Members of your project can open it |

The event has no case list. Read the full report with `GET /v1/workflow-runs/:id` or `GET /v1/workflow-runs/:id/report.md`, using `runId`. A [CI key](/concepts/api-keys#scope-full-or-ci) can read both.
