# Webhook events reference

A v2 [webhook](/integrations/webhooks/) receives an HTTPS `POST` for each event it subscribes to. This page lists
every event and its payload. How to create webhooks, verify signatures and handle retries is on the
[Webhooks](/integrations/webhooks/) page.

## The envelope

Every delivery body has the same five members:

| Member | Type | Meaning |
|---|---|---|
| `id` | string | The delivery id, `d_` + 32 hex characters. Same as the `HT-Delivery` header; stable across retries and redeliveries. Deduplicate on it. |
| `event` | string | The event name (case-sensitive). |
| `occurredAt` | integer | When the event happened, Unix seconds (not when this attempt was sent). |
| `apiVersion` | string | Always `"v2"`. |
| `data` | object | The event's payload - see below. |

Members with no value are omitted. New members may be added; ignore ones you do not know.

Headers: `HT-Event`, `HT-Delivery`, `HT-Webhook`, `HT-Attempt`, `HT-Signature`, and the Standard Webhooks trio
`webhook-id`, `webhook-timestamp`, `webhook-signature`, plus any custom headers you configured.

## Event list

| Event | Fires when | Addressed to | Retried |
|---|---|---|---|
| `monitor.down` | An alert-grade down transition - after recheck confirmation and any alert delay. | webhooks whose scope covers the monitor | yes |
| `monitor.up` | The matching recovery. | scope | yes |
| `monitor.repeatedlyDown` | The "still down" reminder tier is reached. | scope | yes |
| `incident.opened` | A down episode opens - every episode, including one inside a maintenance window. | scope | yes |
| `incident.closed` | The episode resolves. | scope | yes |
| `monitor.created` | A monitor is created (one event per monitor, including each one in a bulk create). | scope | no |
| `monitor.updated` | A monitor's configuration changes (one per monitor, including bulk updates). | scope | no |
| `monitor.deleted` | A monitor is deleted (one per monitor, including bulk deletes). | scope | no |
| `maintenance.ended` | A maintenance window ends on schedule (`endedEarly: false`) or is cancelled while active (`endedEarly: true`). | scope | on schedule: yes; cancel: no |
| `certificate.expiring` | An SSL certificate check crosses a warning threshold. | scope | yes |
| `domain.expiring` | A domain expiry check crosses a warning threshold. | scope | yes |
| `contact.confirmed` | A contact becomes confirmed. | every enabled webhook of the account subscribed to it (scope does not apply) | no |
| `contact.updated` | A contact's configuration changes, including bulk updates and a create that resolves to an existing contact. | account-wide, as above | no |
| `job.completed` | An async job reaches `succeeded`, `partial`, `failed` or `cancelled`. **Callback only.** | the webhook named in the job request's `callback` | no |
| `job.progress` | A running job reports progress (throttled). **Callback only**, with `callback.on: "progress"`. | the callback webhook | no |

There is no `maintenance.started` event. `GET /webhook` returns the live catalogue in `summary.eventTypes`, and the
OpenAPI document (`/openapi/v2.json`) publishes each payload schema under `webhooks`.

## monitor.down, monitor.up, monitor.repeatedlyDown

| Field | Type | Meaning |
|---|---|---|
| `monitor.id`, `monitor.name`, `monitor.url`, `monitor.type` | string | The monitor. |
| `state` | `up` / `down` | The monitor's state as of this alert (`down` for both down events). |
| `occurredAt` | integer | When the alert was decided. |
| `checkNumber` | integer | The check's number in the monitor's series. |
| `error.code`, `error.message`, `error.codename` | int / string | The failure; on `monitor.up`, the outage that just ended. `codename` is the value to switch on - see [error codes](/reference/error-codes/). |
| `failedAt` | integer | When the failing check ran (`monitor.down` only). |
| `firstFailedAt`, `lastFailedAt` | integer | When the outage began, and its last failing check (`monitor.up`, `monitor.repeatedlyDown`). |
| `failedChecks` | integer | Failing checks during the outage (`monitor.up`, `monitor.repeatedlyDown`). |
| `downtimeSec` | integer | How long the outage lasted. |
| `recheck[]` | array | What each location saw during the confirming recheck: `location` (`id`, `country`, `region`, `city`, `ip`), `state`, `error`. Empty on `monitor.repeatedlyDown`. |

```json
{
  "id": "d_e944b34a57924935968d32ea94a07832",
  "event": "monitor.down",
  "occurredAt": 1790000120,
  "apiVersion": "v2",
  "data": {
    "monitor": { "id": "79c21af5-0f43-4262-b7b6-841cd3b39b19", "name": "Shop", "url": "https://shop.example.com/", "type": "http" },
    "state": "down",
    "occurredAt": 1790000120,
    "failedAt": 1790000060,
    "error": { "code": 503, "message": "Service Unavailable", "codename": "Http 503" },
    "recheck": [
      { "location": { "country": "Germany", "city": "Frankfurt" }, "state": "down",
        "error": { "code": 503, "message": "Service Unavailable", "codename": "Http 503" } }
    ]
  }
}
```

## incident.opened, incident.closed

| Field | Type | Meaning |
|---|---|---|
| `incidentId` | string | The incident id - use it with `GET /monitor/incident/{id}`. The same id on open and close. |
| `monitorId` | string | The monitor. |
| `start` | integer | When the episode began. |
| `cause` | string | The failure that opened it, as described by the engine. |
| `underMaintenance` | boolean | True when it began inside a maintenance window (no alert was sent). |
| `end` | integer | When it resolved (`incident.closed` only). |
| `durationSec` | integer | Its length (`incident.closed` only). |
| `checkCount` | integer | Failing checks observed; 0 when not counted (`incident.closed` only). |

## monitor.created, monitor.updated, monitor.deleted

- `monitor.created` and `monitor.updated`: `data` is the full monitor, exactly as `GET /monitor/{id}` returns it.
- `monitor.deleted`: `data` is the delete receipt - `id`, `deleted`, `type`, `name`, `url` and `cascaded` (what was
  removed with it).

## maintenance.ended

| Field | Type | Meaning |
|---|---|---|
| `maintenanceId` | string | The window - use it with `GET /maintenance/{id}`. |
| `name` | string | The window's name. |
| `from` | integer | The window's start. |
| `to` | integer | The scheduled end (not reached when cancelled early). |
| `endedAt` | integer | When it actually ended. |
| `endedEarly` | boolean | True when cancelled while active. |
| `monitorIds` | array | The window's monitors that this webhook's scope covers. |

## certificate.expiring

| Field | Type | Meaning |
|---|---|---|
| `monitorId` | string | The monitor whose certificate is expiring. |
| `notAfter` | integer | When the certificate expires. |
| `daysLeft` | integer | Days until expiry. |
| `issuer` | string | The issuing authority, when known. |
| `threshold` | integer | The warning threshold crossed, in days. |
| `endpoint` | string | `host:port`. |

## domain.expiring

| Field | Type | Meaning |
|---|---|---|
| `monitorId` | string | The monitor whose domain is expiring. |
| `expiresAt` | integer | When the registration expires. |
| `daysLeft` | integer | Days until expiry. |
| `registrar` | string | The registrar, when known. |
| `sharedDomain` | string | The registered domain the expiry belongs to (it can answer for several monitors). |
| `threshold` | integer | The warning threshold crossed, in days. |

## contact.confirmed, contact.updated

- `contact.confirmed`: `contactId`, `type` (`email`, `sms`, ...), `address`, `confirmedAt`, `via` (`code` - a code
  was entered; `inherit` - the address was already confirmed on the account).
- `contact.updated`: `data` is the full contact, as `GET /contact/{id}` returns it. This event is the only way to hear
  about contact edits - `GET /contact?updatedSince=` does not show them.

## job.completed, job.progress

| Field | Type | Meaning |
|---|---|---|
| `jobId` | string | Use it with `GET /job/{id}`. |
| `kind` | string | What the job does (`monitor.bulkCreate`, `contact.bulkWrite`, ...). |
| `state` | string | `job.completed`: `succeeded`, `partial`, `failed` or `cancelled`. `job.progress`: `queued` or `running`. |
| `summary` | object | Counts of created, updated, skipped, failed, deleted items. |
| `progress` | object | `done` and `total`. |
| `created`, `finishedAt` | integer | Submission and completion time (`job.completed`). |
| `error` | string | Why the whole job failed, if it did (`job.completed`). |
| `results[]`, `resultsTruncated` | array / boolean | The first page of per-item receipts (`job.completed`). |
| `resultsUrl` | string | Where to read every receipt, paged. |

## Related

- [Webhooks](/integrations/webhooks/)
- [REST API v2](/integrations/rest-api/)
- [Error codes reference](/reference/error-codes/)
- [What a maintenance window suppresses](/maintenance/what-it-suppresses/)
