# Webhooks

A **webhook channel** sends each alert notification as a JSON `POST` request to a URL that you choose. Use it to connect Spectate to your own tools, for example a chat bot or a ticket system. This page describes the request, the payloads and the delivery rules. To receive alerts from other tools in Spectate, use an [alert source](/alerting/alert-sources) instead.

## Add a webhook channel

1. In the dashboard, select **Alerting**.
2. Select **New channel**.
3. Under **Integration**, select **Webhook**.
4. Enter a **Name** and the **Webhook URL**.
5. To send headers, select **Add header** and enter a name and a value.
6. Select **Create channel**.
7. Add the channel to an [escalation path](/alerting/escalation-paths) with a **Notify channel** step.

With the [public API](/api-reference/channels), send `type: "webhook"` and a `webhook` object with `url` and up to 20 `headers`. The URL and the header values are write-only. The API never returns them.

## The request

```http
POST /spectate HTTP/1.1
Host: hooks.example.com
Content-Type: application/json
User-Agent: Spectate/2.0
Authorization: Bearer YOUR_SECRET

{"event": "alert.fired", "alert": {...}, "chart_url": "..."}
```

| Part | Value |
|---|---|
| Method | `POST` |
| Body | JSON, UTF-8 |
| `Content-Type` | `application/json` |
| `User-Agent` | `Spectate/2.0` |
| Your headers | Each header that you added to the channel |

Spectate does not sign webhook requests. To make sure that a request comes from Spectate, add a secret header to the channel, for example `Authorization: Bearer YOUR_SECRET`, and check it in your receiver. Use an `https://` URL so that the secret is encrypted in transit.

You cannot set the headers `Connection`, `Content-Length`, `Host` and `Transfer-Encoding`.

## Events

| `event` | Sent when |
|---|---|
| `alert.fired` | An escalation step sends a firing alert to the channel. |
| `alert.resolved` | An alert that the channel got resolves, on its own or by hand. |
| `channel.test` | Someone sends a test from the dashboard or the API. |

Spectate does not send a webhook when someone acknowledges an alert. A channel gets one `alert.fired` for each escalation step that notifies it, and at most one `alert.resolved` per alert.

### `alert.fired`

```json
{
  "event": "alert.fired",
  "alert": {
    "uuid": "9d3f6c2a-6a1e-4f8e-9b7d-2c5e8f1a4b30",
    "title": "Checkout API is down",
    "severity": "critical",
    "source_type": "uptime",
    "fired_at": "2026-10-27T09:14:03+00:00",
    "metadata": {
      "monitor_url": "https://api.example.com/health",
      "protocol": "https",
      "error": "Connection timed out after 30 seconds",
      "location": "eu",
      "checked_at": "2026-10-27T09:14:01+00:00"
    }
  },
  "chart_url": "https://api.spectate.net/alert-charts/9d3f6c2a-6a1e-4f8e-9b7d-2c5e8f1a4b30/fired.png?expires=1764407643&signature=..."
}
```

### `alert.resolved`

```json
{
  "event": "alert.resolved",
  "alert": {
    "uuid": "9d3f6c2a-6a1e-4f8e-9b7d-2c5e8f1a4b30",
    "title": "Checkout API is down",
    "severity": "critical",
    "source_type": "uptime",
    "fired_at": "2026-10-27T09:14:03+00:00",
    "resolved_at": "2026-10-27T09:31:47+00:00",
    "metadata": {
      "monitor_url": "https://api.example.com/health",
      "protocol": "https",
      "error": "Connection timed out after 30 seconds",
      "location": "eu",
      "checked_at": "2026-10-27T09:14:01+00:00"
    }
  },
  "chart_url": "https://api.spectate.net/alert-charts/9d3f6c2a-6a1e-4f8e-9b7d-2c5e8f1a4b30/resolved.png?expires=1764408707&signature=..."
}
```

### `channel.test`

```json
{
  "event": "channel.test",
  "title": "Test notification for Ops webhook",
  "message": "200 OK - Test notification sent successfully"
}
```

### Fields

| Field | Type | Description |
|---|---|---|
| `event` | string | `alert.fired`, `alert.resolved` or `channel.test`. |
| `alert.uuid` | string | The alert ID. It is the same in both events of one alert, in the dashboard URL `/alerts/{uuid}` and in the [public API](/api-reference/alerts). |
| `alert.title` | string | What is wrong, for example `Checkout API is down`. |
| `alert.severity` | string | `critical`, `high`, `medium` or `low`. |
| `alert.source_type` | string | `uptime`, `performance`, `certificate`, `dns`, `host_metric`, `domain`, `status_page` or `external`. See [alerts](/alerting/alerts). |
| `alert.fired_at` | string | When the alert fired, ISO 8601. |
| `alert.resolved_at` | string | When the alert resolved, ISO 8601. Only in `alert.resolved`. |
| `alert.metadata` | object or `null` | Details from the source. The keys depend on the source type. |
| `chart_url` | string or `null` | A PNG chart of the data around the alert. `null` when there is no chart. |

The keys in `alert.metadata` are not fixed. For an uptime alert they include `monitor_url`, `protocol`, `error`, `status_code`, `location`, `response_time` and `checked_at`, when the check had them. For an alert from an [alert source](/alerting/alert-sources), they include `provider`, `labels`, `links` and the metadata that the tool sent. Read the keys that you need, and ignore keys that you do not know.

The `chart_url` link is signed and works for 30 days. Uptime, performance and host metric alerts can have a chart.

## Delivery

| Rule | Value |
|---|---|
| Connect timeout | 2 seconds |
| Request timeout | 5 seconds |
| Success | Any `2xx` response |
| Redirects | Not followed. A `3xx` response is a failure that Spectate does not retry. |
| Tries | 3: at once, after 30 seconds, and 2 minutes after that |
| No retry | HTTP `401`, `403`, `404` and `410` |

Answer fast, with a `2xx` status, and do slow work after you answer. A request that takes longer than 5 seconds fails, even if your receiver processed it. After a timeout, Spectate sends the same event again. Use `event` and `alert.uuid` to find repeated deliveries.

A webhook that keeps failing is disabled automatically. See [when Spectate disables a channel](/alerting/channels#when-spectate-disables-a-channel).

## URL rules

Spectate sends webhooks only to public addresses. A URL must meet these rules:

- The scheme is `http` or `https`.
- The URL has no user name or password in it.
- The host is not `localhost`, and does not end in `.localhost` or `.internal`.
- Every IPv4 and IPv6 address of the host is public. Private, reserved and carrier-grade NAT (`100.64.0.0/10`) addresses are refused, as are IPv4-mapped and NAT64 IPv6 addresses.

Spectate checks the URL when you save the channel and again before each request. The request goes to the IP address that Spectate checked. A DNS change after the check cannot send it to another address. If the URL redirects, use the final URL in the channel.

## Related

- [Channels](/alerting/channels): all channel types, tests and auto-disable.
- [Alert sources](/alerting/alert-sources): send alerts from your tools into Spectate.
