Webhooks

Nines sends a signed JSON POST to your endpoint whenever an incident is created, updated, or resolved. Use webhooks to route alerts to Slack, PagerDuty, or any custom system.

Configuring a webhook

  1. Go to Settings → Notifications → Add channel → Webhook.
  2. Enter the destination URL.
  3. Optionally enter a signing secret. When set, every request includes an X-Webhook-Signature header so you can verify authenticity.
  4. Save the channel.

Webhook formats

When configuring a webhook channel, you can choose how Nines formats the request body. Set the format field in the channel configuration to one of:

  • generic (default) — a JSON object containing all incident fields. Use this for custom integrations and general-purpose HTTP endpoints. See the payload schema below.
  • discord — a Discord-compatible Incoming Webhook payload using an embed with colour-coded status, monitor details, and a link to the incident. Point this at a Discord channel webhook URL.
  • slack — a Slack Block Kit payload with a header block, status fields, and an optional link to the incident. Point this at a Slack Incoming Webhook URL.
  • custom — the body is rendered from a Go text/template string that you supply in the template field of the channel configuration. The template receives an IncidentContext value exposing .Incident, .Monitor, .Org, .Event, .AffectedRegions, and .BaseURL. Use this when you need a completely custom payload shape.

Request format

Nines sends an HTTP POST with the following headers:

Content-Type: application/json
X-Webhook-Signature: sha256=<hex-digest>   (only when a secret is configured)

Signing and verification

When you configure a signing secret, Nines computes an HMAC-SHA256 over the raw request body using your secret as the key and includes the result as X-Webhook-Signature: sha256=<hex-digest>.

To verify on your end:

// Pseudocode
expected := "sha256=" + hex(hmac_sha256(secret, request_body))
if not constant_time_equal(expected, request.header["X-Webhook-Signature"]):
    reject(401)

Always use a constant-time comparison to prevent timing attacks.

Retry behavior

If your endpoint returns a non-2xx status or the request fails with a network error, Nines retries the delivery up to 3 times with exponential backoff:

  • Attempt 1: immediate
  • Attempt 2: 1 second delay
  • Attempt 3: 2 second delay

After 3 failed attempts the delivery is abandoned and an error is logged. Make your endpoint idempotent — Nines may deliver the same event more than once during retries.

Payload schema

Every webhook payload shares a common top-level shape:

{
  "event":            string,    // "incident.created" | "incident.updated" | "incident.resolved"
  "incident":         object,    // incident fields (see below)
  "monitor":          object,    // the monitor that triggered the incident
  "org_name":         string,    // display name of your organisation
  "org_slug":         string,    // URL slug of your organisation
  "affected_regions": [string],  // region codes currently reporting down (empty for burn-rate)
  "timestamp":        string     // RFC 3339 UTC timestamp
}

The incident object fields:

{
  "id":          string,          // ULID
  "monitor_id":  string,          // ULID of the parent monitor
  "status":      string,          // "investigating" | "identified" | "resolved"
  "details":     string,          // human-readable description
  "started_at":  string,          // RFC 3339 UTC
  "resolved_at": string | null,   // RFC 3339 UTC, null while open
  "created_at":  string,          // RFC 3339 UTC
  "updated_at":  string,          // RFC 3339 UTC
  "sli_type":    string | null    // "availability_burn" | "latency_burn" | null for region failures
}

Example payloads