Signed webhooks

Signed webhooks

Send verified incident events to your own applications.
6 min read Updated Sep 5, 2026 A signed webhook destination with verified payloads and retry handling

Connect an endpoint

Open Integrations, find Signed webhooks, and choose Set up integration. Add a public HTTPS endpoint on port 443 and select confirmed outages, recoveries, or both. Save the signing secret shown after creation: it cannot be retrieved later. Use Connected → Test to send a webhook.test event.

Endpoints must resolve only to public IPv4 addresses. Private networks, IPv6, URL credentials, fragments, and redirects are not supported. Endpoint credentials and signing secrets are encrypted at rest.

Verify a delivery

Requests are JSON POSTs with three headers:

  • X-Uptime-Id: stable notification identifier; deduplicate this across retries.
  • X-Uptime-Timestamp: Unix timestamp in seconds for this attempt.
  • X-Uptime-Signature: v1= followed by the hexadecimal HMAC-SHA256 digest of timestamp + "." + rawBody, using your signing secret exactly as displayed.

Read the raw body before JSON parsing, reject timestamps more than five minutes old (or materially in the future), and compare signatures in constant time. Do not log the secret.

import { createHmac, timingSafeEqual } from 'node:crypto'

export function verifyWebhook(rawBody, timestamp, signature, secret) {
  if (!/^\d+$/.test(timestamp ?? '')) return false
  if (Math.abs(Date.now() / 1000 - Number(timestamp)) > 300) return false
  const expected = Buffer.from('v1=' + createHmac('sha256', secret)
    .update(timestamp + '.').update(rawBody).digest('hex'))
  const actual = Buffer.from(signature ?? '')
  return actual.length === expected.length && timingSafeEqual(actual, expected)
}

Event payload

{
  "id": "notification-uuid",
  "type": "incident_confirmed",
  "createdAt": "2026-09-05T12:00:00.000Z",
  "data": {
    "monitor": "Checkout API",
    "target": "https://api.example.com/health",
    "summary": "Regional failure threshold reached",
    "openedAt": "2026-09-05T12:00:00.000Z",
    "resolvedAt": null
  }
}

Incident types are incident_confirmed and incident_resolved. Test events have type webhook.test and a data.message string. createdAt reflects the incident opening or recovery time. Treat the notification ID as opaque.

Return any 2xx response within ten seconds. Failed incident deliveries retry up to five total attempts with exponential backoff (5, 10, 20, 40 seconds between attempts). Duplicate deliveries are possible. Tests run once and report their result immediately. Pausing or removing a destination stops future deliveries; already in-flight requests may finish. To replace a lost or compromised signing secret, create a replacement destination and remove the old one.