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

# Webhook payloads

Envelope format, event catalog, and signature verification for outgoing webhooks.

# Webhook payloads

For setup, retry policy, and replay see **[Integrations → Webhooks](/integrations/webhooks)**. This page is a reference for the payload shapes on the wire.

## Envelope

Every webhook request uses the same versioned envelope. `data` varies per event.

```json
{
  "api_version": "1",
  "event": "monitor.down",
  "delivery_id": "whd_8k3nP2oQ1xR7vL9m",
  "delivered_at": "2026-03-07T14:22:00.812Z",
  "data": { /* event-specific */ }
}
```

Headers on every request:

| Header | Value |
| --- | --- |
| `X-Happyuptime-Event` | Public event name |
| `X-Happyuptime-Delivery` | Unique per-attempt delivery id |
| `X-Happyuptime-Signature` | `sha256=<hex>` HMAC-SHA256 over the raw body |
| `X-Happyuptime-Delivery-Retry` | Present only on manual replays |

## Events

### `monitor.down` · `monitor.up` · `monitor.degraded`

```json
{
  "monitor": {
    "id": "mon_abc123",
    "name": "API",
    "url": "https://api.example.com/health",
    "type": "http",
    "status": "down"
  },
  "check": {
    "region": "us-east",
    "regions": null,
    "status_code": 503,
    "response_time_ms": 8421,
    "error": "Service Unavailable"
  },
  "ssl_expiry_days": null,
  "domain_expiry_days": null,
  "down_duration": null,
  "incident": null,
  "dashboard_url": "https://happyuptime.com/dashboard/monitors/mon_abc123"
}
```

`monitor.up` includes `down_duration` (human-readable). `monitor.ssl_expiry` / `monitor.domain_expiry` include `ssl_expiry_days` / `domain_expiry_days`.

### `incident.created` · `incident.updated` · `incident.acknowledged` · `incident.resolved`

```json
{
  "incident": {
    "id": "inc_xyz789",
    "title": "API elevated error rate",
    "status": "resolved",
    "severity": "major",
    "started_at": "2026-03-07T14:12:03Z",
    "resolved_at": "2026-03-07T14:55:02Z",
    "acknowledged_at": "2026-03-07T14:18:44Z",
    "acknowledged_by": "usr_ops1"
  },
  "update": {
    "id": "upd_001",
    "status": "resolved",
    "message": "Root cause: stale cache node. Drained + restarted.",
    "is_internal": false,
    "author_id": "usr_ops1",
    "created_at": "2026-03-07T14:55:02Z"
  },
  "monitors": [{ "id": "mon_abc123", "name": "API", "status": "up" }],
  "dashboard_url": "https://happyuptime.com/dashboard/incidents/inc_xyz789",
  "status_page_url": null
}
```

`update` is `null` for `incident.created` and `incident.acknowledged` unless the user posted an initial message.

### `vendor.down` · `vendor.degraded` · `vendor.resolved`

```json
{
  "vendor": { "name": "AWS", "category": "cloud" },
  "incident": {
    "title": "Increased API error rates — US-EAST-1",
    "severity": "major",
    "status": "investigating",
    "source_url": "https://status.aws.amazon.com/"
  }
}
```

## Signature verification

```javascript
import crypto from "crypto";

function verify(req, secret) {
  const header = req.headers["x-happyuptime-signature"];
  if (!header) return false;
  const [algo, hex] = header.split("=");
  if (algo !== "sha256") return false;
  const expected = crypto.createHmac("sha256", secret).update(req.rawBody).digest("hex");
  const a = Buffer.from(hex);
  const b = Buffer.from(expected);
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}
```

Always compare in constant time. Use the **raw** request body — whitespace/ordering affects the hash.

## Idempotency

`delivery_id` is stable across retries — use it as an idempotency key. Automatic retries reuse the original envelope; manual replays (`POST /alerts/log/:id/retry`) also keep the same `delivery_id`.