Webhook Configuration

Route Statusly events to Slack, Discord, your CRM, or any HTTP endpoint in real time.

Quick Start

1. Create a Webhook Endpoint

Navigate to Settings → Integrations → Webhooks in your Statusly dashboard. Click New Webhook and provide a publicly reachable HTTPS URL. Statusly sends a POST request every time a selected event occurs — your endpoint must respond with a 2xx status within 5 seconds.

For Slack, use an Incoming Webhook URL from Slack → Apps → Incoming Webhooks (e.g., https://hooks.slack.com/services/T04ABC123/B08XYZ456/wKj9mNpQrStUvWxYz). For Discord, use a channel webhook URL from Channel Settings → Integrations → Webhooks (e.g., https://discord.com/api/webhooks/1098765432109876543/aBcDeFgHiJkLmNoPqRsTuVwXyZ).

Event Selection

Choose which events trigger your webhook. Available types: incident.created, incident.resolved, incident.updating, check.failed, check.recovered, maintenance.scheduled, maintenance.completed. Select all or pick individually per endpoint.

Retry Policy

If your endpoint returns 4xx or 5xx, Statusly retries up to 5 times with exponential backoff: 10s, 30s, 2m, 10m, 30m. After the final failure, the event is logged under Webhooks → Failed Deliveries for manual replay.

Payload Formatting

All payloads are sent as application/json with UTF-8 encoding. You can enable the Slack Block Kit toggle to wrap the JSON in a Slack-compatible message block automatically — no custom formatting needed.

Reference

2. Event Payload Structure

Every webhook delivery contains a single JSON object. Below is a real incident.created event as it would arrive at your endpoint.

Example: incident.created

{
  "id": "evt_9f8e7d6c5b4a3210",
  "type": "incident.created",
  "timestamp": "2025-06-14T09:32:17Z",
  "account": {
    "id": "acc_7721904",
    "name": "CloudRetail Ltd",
    "plan": "Business"
  },
  "incident": {
    "id": "inc_4421a8b3",
    "title": "Checkout API returning 502",
    "status": "investigating",
    "severity": "major",
    "affected_checks": [
      {
        "id": "chk_88f2e1d0",
        "name": "POST /api/v2/checkout",
        "url": "https://api.cloudretail.ru/api/v2/checkout",
        "last_response_time_ms": 12450,
        "last_status_code": 502,
        "location": "Moscow"
      },
      {
        "id": "chk_99a3f2e1",
        "name": "POST /api/v2/checkout",
        "url": "https://api.cloudretail.ru/api/v2/checkout",
        "last_response_time_ms": 11890,
        "last_status_code": 502,
        "location": "Frankfurt"
      }
    ],
    "created_by": {
      "id": "usr_551022",
      "name": "Statusly Bot",
      "type": "automated"
    }
  },
  "webhook": {
    "id": "wh_33c1b2a4",
    "name": "Slack #ops-incidents"
  }
}

check.failed

Sent when a monitored endpoint returns a non-2xx response or exceeds the timeout threshold (default 10s). Includes check.last_response_time_ms, check.last_status_code, and check.location.

incident.resolved

Sent when an incident transitions to resolved. Payload mirrors incident.created but adds incident.duration_seconds and incident.affected_checks_count.

Security

3. Request Signing & Verification

Every webhook request includes an HMAC-SHA256 signature so you can verify it originated from Statusly and was not tampled with in transit.

Signature Headers

Each POST request carries two custom headers:

  • Statusly-Signature — HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret (e.g., whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6)
  • Statusly-Timestamp — Unix epoch milliseconds when the request was signed (e.g., 1718360537000)

Verification Steps

  1. Read the raw request body and the Statusly-Signature header.
  2. Compute HMAC_SHA256(webhook_secret, raw_body) on your server.
  3. Compare the computed digest with the header value using a constant-time string comparison.
  4. Reject the request if the signature does not match or if Statusly-Timestamp is more than 5 minutes old (prevents replay attacks).

Node.js Verification Example

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'utf8'),
    Buffer.from(expected, 'utf8')
  );
}

// Usage in Express
app.post('/webhooks/statusly', (req, res) => {
  const sig = req.headers['statusly-signature'];
  const secret = process.env.STATUSLY_WEBHOOK_SECRET;

  if (!verifySignature(JSON.stringify(req.body), sig, secret)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  console.log('Event:', req.body.type);
  res.status(200).send('OK');
});

Managing Your Secret

Your webhook secret is generated automatically when you create a webhook. You can rotate it at any time from the webhook settings — Statusly will honor both the old and new secret for 24 hours to avoid missed deliveries. If you lose the secret, generate a new one; existing deliveries will fail until your server is updated.

Webhook Configuration

Route Statusly events to Slack, Discord, your CRM, or any HTTP endpoint in real time.

Quick Start

1. Create a Webhook Endpoint

Navigate to Settings → Integrations → Webhooks in your Statusly dashboard. Click New Webhook and provide a publicly reachable HTTPS URL. Statusly sends a POST request every time a selected event occurs — your endpoint must respond with a 2xx status within 5 seconds.

For Slack, use an Incoming Webhook URL from Slack → Apps → Incoming Webhooks (e.g., https://hooks.slack.com/services/T04ABC123/B08XYZ456/wKj9mNpQrStUvWxYz). For Discord, use a channel webhook URL from Channel Settings → Integrations → Webhooks (e.g., https://discord.com/api/webhooks/1098765432109876543/aBcDeFgHiJkLmNoPqRsTuVwXyZ).

Event Selection

Choose which events trigger your webhook. Available types: incident.created, incident.resolved, incident.updating, check.failed, check.recovered, maintenance.scheduled, maintenance.completed. Select all or pick individually per endpoint.

Retry Policy

If your endpoint returns 4xx or 5xx, Statusly retries up to 5 times with exponential backoff: 10s, 30s, 2m, 10m, 30m. After the final failure, the event is logged under Webhooks → Failed Deliveries for manual replay.

Payload Formatting

All payloads are sent as application/json with UTF-8 encoding. You can enable the Slack Block Kit toggle to wrap the JSON in a Slack-compatible message block automatically — no custom formatting needed.

Reference

2. Event Payload Structure

Every webhook delivery contains a single JSON object. Below is a real incident.created event as it would arrive at your endpoint.

Example: incident.created

{
  "id": "evt_9f8e7d6c5b4a3210",
  "type": "incident.created",
  "timestamp": "2025-06-14T09:32:17Z",
  "account": {
    "id": "acc_7721904",
    "name": "CloudRetail Ltd",
    "plan": "Business"
  },
  "incident": {
    "id": "inc_4421a8b3",
    "title": "Checkout API returning 502",
    "status": "investigating",
    "severity": "major",
    "affected_checks": [
      {
        "id": "chk_88f2e1d0",
        "name": "POST /api/v2/checkout",
        "url": "https://api.cloudretail.ru/api/v2/checkout",
        "last_response_time_ms": 12450,
        "last_status_code": 502,
        "location": "Moscow"
      },
      {
        "id": "chk_99a3f2e1",
        "name": "POST /api/v2/checkout",
        "url": "https://api.cloudretail.ru/api/v2/checkout",
        "last_response_time_ms": 11890,
        "last_status_code": 502,
        "location": "Frankfurt"
      }
    ],
    "created_by": {
      "id": "usr_551022",
      "name": "Statusly Bot",
      "type": "automated"
    }
  },
  "webhook": {
    "id": "wh_33c1b2a4",
    "name": "Slack #ops-incidents"
  }
}

check.failed

Sent when a monitored endpoint returns a non-2xx response or exceeds the timeout threshold (default 10s). Includes check.last_response_time_ms, check.last_status_code, and check.location.

incident.resolved

Sent when an incident transitions to resolved. Payload mirrors incident.created but adds incident.duration_seconds and incident.affected_checks_count.

Security

3. Request Signing & Verification

Every webhook request includes an HMAC-SHA256 signature so you can verify it originated from Statusly and was not tampered with in transit.

Signature Headers

Each POST request carries two custom headers:

  • Statusly-Signature — HMAC-SHA256 hex digest of the raw request body, keyed with your webhook secret (e.g., whsec_a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6)
  • Statusly-Timestamp — Unix epoch milliseconds when the request was signed (e.g., 1718360537000)

Verification Steps

  1. Read the raw request body and the Statusly-Signature header.
  2. Compute HMAC_SHA256(webhook_secret, raw_body) on your server.
  3. Compare the computed digest with the header value using a constant-time string comparison.
  4. Reject the request if the signature does not match or if Statusly-Timestamp is more than 5 minutes old (prevents replay attacks).

Node.js Verification Example

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload, 'utf8')
    .digest('hex');

  return crypto.timingSafeEqual(
    Buffer.from(signature, 'utf8'),
    Buffer.from(expected, 'utf8')
  );
}

// Usage in Express
app.post('/webhooks/statusly', (req, res) => {
  const sig = req.headers['statusly-signature'];
  const secret = process.env.STATUSLY_WEBHOOK_SECRET;

  if (!verifySignature(JSON.stringify(req.body), sig, secret)) {
    return res.status(401).send('Invalid signature');
  }

  // Process the event
  console.log('Event:', req.body.type);
  res.status(200).send('OK');
});

Managing Your Secret

Your webhook secret is generated automatically when you create a webhook. You can rotate it at any time from the webhook settings — Statusly will honor both the old and new secret for 24 hours to avoid missed deliveries. If you lose the secret, generate a new one; existing deliveries will fail until your server is updated.