Webhooks

Outbound webhooks forward Happy Uptime events to your own infrastructure — internal Slack bots, Datadog, ServiceNow, custom dashboards, CI pipelines. Every payload is HMAC-SHA256 signed, wrapped in a versioned envelope, and recorded in the alert log so you can inspect and replay failed deliveries.

Add a webhook

Dashboard → Alerts → Channels → Add channel → Webhook, or via the API:

bash
curl -X POST https://happyuptime.com/api/v1/alerts/channels \ -H "Authorization: Bearer $HU_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "name": "Ops bus", "type": "webhook", "config": { "url": "https://hooks.example.com/happyuptime", "headers": { "X-Internal-Tag": "prod" } } }'

The response includes the channel's auto-generated signing_secret (prefix whsec_). Store it — it's the only way to verify request authenticity.

To rotate the secret:

bash
curl -X POST https://happyuptime.com/api/v1/alerts/channels/$CHANNEL_ID/rotate-secret \ -H "Authorization: Bearer $HU_API_KEY"

Subscribing to events

Webhooks receive events based on how they're attached:

  • Monitor events (monitor.down, monitor.up, monitor.degraded) — attach the channel via an alert rule on a specific monitor, or mark it as default and new monitors auto-link.

  • Incident events (incident.created, incident.updated, incident.resolved, incident.acknowledged) — subscribe at the org level:

    bash
    curl -X POST https://happyuptime.com/api/v1/alerts/project-channels \ -H "Authorization: Bearer $HU_API_KEY" \ -H "Content-Type: application/json" \ -d '{"alert_channel_id": "'"$CHANNEL_ID"'", "alert_type": "incident"}'
  • Vendor events (vendor.down, vendor.degraded, vendor.resolved) — subscribe at the org level with alert_type: "dependency_incident". Per-dependency overrides are also supported in the dependency UI.

Payload envelope

Every webhook POST uses this envelope. The data block varies per event type.

json
{ "api_version": "1", "event": "monitor.down", "delivery_id": "whd_8k3nP2oQ1xR7vL9m", "delivered_at": "2026-04-22T14:32:11.812Z", "data": { "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" }}

Incident envelope

json
{ "api_version": "1", "event": "incident.resolved", "delivery_id": "whd_9m4oQ3pR2yS8wN0n", "delivered_at": "2026-04-22T14:55:02.004Z", "data": { "incident": { "id": "inc_xyz789", "title": "API elevated error rate", "status": "resolved", "severity": "major", "started_at": "2026-04-22T14:12:03Z", "resolved_at": "2026-04-22T14:55:02Z", "acknowledged_at": "2026-04-22T14: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-04-22T14:55:02Z" }, "monitors": [ { "id": "mon_abc123", "name": "API", "status": "up" } ], "dashboard_url": "https://happyuptime.com/dashboard/incidents/inc_xyz789", "status_page_url": null }}

Vendor envelope

json
{ "api_version": "1", "event": "vendor.down", "delivery_id": "whd_abc", "delivered_at": "2026-04-22T14:32:11Z", "data": { "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/" } }}

Events

EventWhen it fires
monitor.downMonitor transitions to down
monitor.upMonitor recovers to up
monitor.degradedMonitor enters degraded state
monitor.ssl_expirySSL cert within warning window
monitor.domain_expiryDomain registration within warning window
incident.createdIncident opened (manual or auto)
incident.updatedStatus update posted or severity changed
incident.acknowledgedSomeone acknowledged the incident
incident.resolvedIncident marked resolved
vendor.downTracked vendor (AWS/Stripe/etc.) enters a new incident
vendor.degradedTracked vendor enters a degraded state
vendor.resolvedTracked vendor incident closed

Signature verification

Every request carries three headers:

HeaderValue
X-Happyuptime-EventPublic event name (e.g. monitor.down)
X-Happyuptime-DeliveryUnique per-attempt delivery id
X-Happyuptime-Signaturesha256=<hex> — HMAC-SHA256 of the raw body using your signing_secret

Verify in Node:

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 use a constant-time compare. Never log the secret.

Idempotency

Use X-Happyuptime-Delivery as your idempotency key. Retries (manual or automatic) reuse the same delivery_id from the original envelope, so you can safely process the same payload once.

Retries

The first attempt fires immediately. On any non-2xx response or request timeout (10s), Happy Uptime retries up to 2 more times within the same delivery:

AttemptDelay
1immediate
21s
35s

Only 5xx and 429 responses trigger a retry. 4xx (except 429) is treated as a hard failure — fix the endpoint and replay.

After 3 failed attempts the delivery is marked failed in the alert log. Channels with 50+ consecutive failed deliveries are auto-paused — you'll see a banner in Alerts → Channels. Resume with:

bash
curl -X POST https://happyuptime.com/api/v1/alerts/channels/$CHANNEL_ID/resume \ -H "Authorization: Bearer $HU_API_KEY"

Managed retries via Hookstream

For exponential backoff over hours and automatic DLQ, set config.hookstream_source_id on the channel. Happy Uptime forwards the signed payload to Hookstream, which handles retries, circuit breakers, and observability.

Replay

Every delivery — including its full request body, response code, and response snippet — is stored in the alert log.

  • Dashboard: Alerts → Log → click row → Resend

  • API:

    bash
    # Inspect a deliverycurl https://happyuptime.com/api/v1/alerts/log/$LOG_ID \ -H "Authorization: Bearer $HU_API_KEY"# Replay — re-sends the original signed bodycurl -X POST https://happyuptime.com/api/v1/alerts/log/$LOG_ID/retry \ -H "Authorization: Bearer $HU_API_KEY"

Replays reuse the original delivery_id so your idempotency layer de-dupes automatically. The replay carries an additional header X-Happyuptime-Delivery-Retry: true.

Debugging

  • No request arriving? Check Alerts → Log for the attempt. 4xx → your endpoint rejected it (auth, body shape). 5xx or timeout → your service errored or was slow.
  • Signature mismatch? Make sure you're hashing the raw request body, not a parsed/re-serialized copy. Whitespace matters.
  • Channel paused? 50 consecutive failures trigger auto-pause. Hit /resume after fixing.
  • Want to test offline? Pipe webhooks through webhook.site or run ngrok in front of localhost.