# Alerts
URL: /docs/cloud/alerts

Watch one metric of a project once an hour and get a signed webhook when it crosses a threshold.

> For AI agents: a documentation index is available at [llms.txt](/llms.txt). Use `.md` for canonical markdown pages; `.mdx` is kept as a backwards-compatible alias on supported URL paths.

An alert rule watches one metric of the project. Once an hour the cloud evaluates the day's totals in UTC, and when a metric is above its threshold it posts a signed JSON payload to the rule's webhook, then stays quiet for the rule's cooldown. Rules live in **Settings › Alerts** and are managed by owners and admins.

## Metrics

| Metric                         | Compares                                                 | Unit   |
| ------------------------------ | -------------------------------------------------------- | ------ |
| `daily_runs`                   | Today's runs                                             | runs   |
| `daily_cost_usd`               | Today's cost, sampling calls included                    | USD    |
| `daily_error_rate`             | Today's share of failed runs                             | %      |
| `daily_incomplete_rate`        | Today's share of runs that did not complete              | %      |
| `period_active_users`          | Active users of the UTC calendar month, for the plan cap | users  |
| `daily_runs_change`            | Today's runs against the mean of the previous seven days | % rise |
| `daily_cost_usd_change`        | Today's cost against the mean of the previous seven days | % rise |
| `daily_incomplete_rate_change` | Today's incomplete rate against the previous seven days  | % rise |

A rule fires when the value is strictly above the threshold. A rate is silent while the day has no runs, and a change metric is silent until three days of history exist.

## A rule

| Field                | Meaning                                                                                                                                             |
| -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- |
| Name                 | Unique in the project, for instance `Daily spend`.                                                                                                  |
| Metric and threshold | One of the metrics above and the value to stay under.                                                                                               |
| Cooldown             | How long to wait before delivering the same rule again, from 5 minutes to 7 days; one day by default. The cooldown starts when a delivery succeeds. |
| Webhook URL          | A public `https://` URL.                                                                                                                            |
| Enabled              | A disabled rule keeps its history and its secret.                                                                                                   |

The signing secret is shown once when the rule is created and again when you rotate it. **Send test** delivers a payload of type `alert.test` right away, so the receiver can be checked before the first real alert.

## The webhook

```
{
  "type": "alert.triggered",
  "project_id": "proj_…",
  "rule": { "id": "alert_…", "name": "Daily spend", "metric": "daily_cost_usd", "threshold": "25.000000" },
  "value": 31.2,
  "day": "2026-09-13",
  "occurred_at": "2026-09-13T17:00:00.000Z"
}
```

The request is a `POST` with `content-type: application/json` and an `x-aui-signature` header of the form `t=<unix seconds>,v1=<hex>`, where the hex is an HMAC SHA-256 of `<t>.<body>` with the rule's secret. Verify it against the raw body before parsing. The sample below rejects a timestamp more than five minutes from now, which closes replays, and a malformed digest, before the constant time comparison. A response in the 2xx range within 10 seconds counts as delivered; redirects are not followed.

```
import { createHmac, timingSafeEqual } from "node:crypto";

const TOLERANCE_SECONDS = 300;

export function verify(secret: string, signature: string, body: string) {
  const parts = Object.fromEntries(
    signature.split(",").map((part) => part.split("=")),
  );
  const timestamp = Number(parts.t);
  const digest = parts.v1 ?? "";
  if (!Number.isInteger(timestamp)) return false;
  if (Math.abs(Date.now() / 1000 - timestamp) > TOLERANCE_SECONDS) return false;
  if (!/^[0-9a-f]{64}$/.test(digest)) return false;
  const expected = createHmac("sha256", secret)
    .update(`${parts.t}.${body}`)
    .digest("hex");
  return timingSafeEqual(Buffer.from(expected), Buffer.from(digest));
}
```

**Recent deliveries** on the same page lists every evaluation that fired, with its status and the response code, and a failed delivery is kept on the rule as its last error.

## Limits

The number of rules is a plan limit: 1 on Free, 20 on Pro and Startup, 100 on Enterprise; see [Plans and pricing](/docs/cloud/pricing).