# Alert webhooks

URL: https://pro-weather.com/docs/alerts-webhooks

> The webhook reference for developers - the JSON body an alert sends, the HMAC signature to verify, and the delivery rules.

This is the developer reference for [alert](https://pro-weather.com/docs/alerts) webhooks: what
arrives at your server, how to prove it came from us, and how delivery
behaves. Setting one up takes no code and is covered in
[Alerts & warnings](https://pro-weather.com/docs/alerts#webhooks); come here when you are writing the
receiver.

## The request

Each notification is a `POST` with a JSON body:

```json
{
  "version": 1,
  "event": "alert.triggered",
  "firedAt": "2026-08-03T04:12:07.881Z",
  "site": {
    "subdomain": "ardooie",
    "title": "Ardooie Meteo",
    "url": "https://ardooie.pro-weather.com"
  },
  "alert": {
    "id": "clx0alertruleid",
    "metric": "tempLow",
    "label": "Temperature below",
    "kind": "threshold",
    "reminder": false
  },
  "reading": { "value": -1.4, "text": "-1.4 °C", "unit": "°C", "detail": null },
  "threshold": { "value": 0, "text": "0.0 °C" },
  "observedAt": 1754194320
}
```

`reading.value` and `reading.text` are `null` for an event alert, which reports
an occurrence rather than a number; `reading.detail` carries the specifics
(which channel, which record, which upload). `alert.reminder` is `true` when
the notification is a repeat of a condition already reported.

## Verifying the signature

Every request carries three headers:

| Header | Meaning |
| --- | --- |
| `X-ProWeather-Event` | `alert.triggered` or `alert.test` |
| `X-ProWeather-Timestamp` | Unix seconds at which we signed the request |
| `X-ProWeather-Signature` | `sha256=` followed by the hex HMAC-SHA256 of `<timestamp>.<raw body>`, keyed with your signing secret |

Compute the same HMAC over the **raw** body (before any JSON parsing) and
compare it with a constant-time comparison. Reject anything whose timestamp is
more than a few minutes old, so a captured request cannot be replayed at you.

```js

function verify(rawBody, headers, secret) {
  const ts = Number(headers['x-proweather-timestamp']);
  if (!Number.isFinite(ts) || Math.abs(Date.now() / 1000 - ts) > 300) return false;
  const expected = `sha256=${createHmac('sha256', secret)
    .update(`${ts}.${rawBody}`)
    .digest('hex')}`;
  const a = Buffer.from(expected);
  const b = Buffer.from(headers['x-proweather-signature'] ?? '');
  return a.length === b.length && timingSafeEqual(a, b);
}
```

The signing secret is generated for you and shown under **Alerts → Webhook**.
Changing the URL keeps the same secret, so a receiver that already verifies
signatures does not break.

## Delivery rules

- The endpoint must be **HTTPS** and cannot point at a private network
  address.
- Redirects are not followed.
- We wait **6 seconds** for a response.
- There is **no retry queue**: a failed delivery leaves the alert unlatched,
  so the next refresh (about 5 minutes) tries again while the condition still
  holds. A receiver that was briefly down misses nothing that is still true.

Use **Send test** under **Alerts → Webhook** to exercise the receiver; the
test request is signed exactly like a real one, with
`X-ProWeather-Event: alert.test`.

## Next steps

- [Alerts & warnings](https://pro-weather.com/docs/alerts): creating the alerts that call this
- [Public API & RSS feed](https://pro-weather.com/docs/public-api): reading your conditions instead
  of being called
- [Troubleshooting](https://pro-weather.com/docs/troubleshooting#webhook-deliveries-arent-reaching-my-server)
  if deliveries fail
