Skip to main content
Pairs with: AI Gateway → Budgets. Budgets enforce hard spend limits; anomaly rules are meant to catch a pattern before a budget breach, though only the spend-spike type is evaluated today.
Available on Enterprise plans. See Open-core licensing for the full Apache 2.0, Enterprise split.
Anomaly rules cannot be created or edited in LangWatch today, and the alerts they raise are not listed anywhere. There is no page for them and no public API, so this page describes what the detector does with a rule and what a rule is made of, not something you can set up right now. Rules that already exist are still evaluated. When one fires, the one place the alert reaches you is a webhook, POSTed to whatever URL the rule’s destination config names, so a rule with no destination fires into a record no screen displays. To watch spend with a limit you can act on, use Budgets, which enforce a hard limit and are available today.
Anomaly rules are the “Detect” layer of the governance control plane. A rule is a definition the event-sourcing detector evaluates against incoming events, producing an AnomalyAlert row when a violation is detected. This page documents what the detector evaluates and what is preview-only, so that a rule which looks “active” is not mistaken for one that fires.

Rule-type coverage

The schema holds every rule type below; the detector evaluates one of them today. A ⏳ Preview rule type is persisted and reads as active, but the detector logs debug and skips it, so no alert is produced. Until the detector slice for each ships, treat such a rule as stored for later rather than as watching anything. If you need detection beyond spend_spike today, the recommended shape is one of:
  • Stand up Prometheus + Grafana against the gateway metrics endpoint, see Cookbook · Prometheus alerts for the alert ruleset.
  • Hook your SIEM to the OTel trace stream LangWatch already emits per project.

What a spend_spike rule is made of

These are the fields a rule carries and the detector reads. No screen collects them today and no public API accepts them; they describe rules that already exist.

Threshold config: spend_spike

Sensible starting values:

Scope coverage

Dispatch coverage

A rule’s Destination config lists where to push the alert. Webhook is the only destination type, and it works today: when a rule fires, the alert row is written first, then POSTed to each destination on the rule.
For Slack or PagerDuty, point a webhook at their own incoming-webhook or events endpoint and reshape the payload on your side. Leave destinations empty for a rule that records alerts without pushing them. A destination config that fails validation is quarantined: no request is sent, and the rule falls back to recording only.

What the webhook receives

A POST with Content-Type: application/json, carrying the rule and the alert it raised:
detail.dispatch reads pending in every payload, because the alert is written before delivery is attempted. The stored row is updated with the delivery outcome afterwards; the payload you receive is the pre-delivery snapshot.

Delivery guarantees

Delivery is best-effort, and the alert row is the authoritative signal. Each destination gets up to three attempts with a five-second timeout each. A 5xx response is retried with exponential backoff; a 4xx fails immediately, on the reading that it is a configuration error rather than a transient one. A destination that never succeeds is logged and recorded on the alert. It does not fail the detector, and it does not block the other destinations. Requests go out through the same server-side-request-forgery guard the ingestion pullers use: the destination is resolved and its address pinned. A URL resolving to a private or link-local address is refused and recorded as a failed delivery when BLOCK_LOCAL_HTTP_CALLS is on, which it is on LangWatch Cloud. Self-hosted, that refusal only applies if you set the flag, and a host listed in ALLOWED_PROXY_HOSTS is allowed either way. The cloud metadata hostnames and addresses themselves are refused regardless of either setting and cannot be allow-listed. An allow-listed name is trusted as written, without a lookup of where it resolves, so only allow-list names you control.

Where an existing rule’s alerts go

In the app: no screen lists them. Each fire is recorded as one alert keyed by (ruleId, triggerWindowStart), so the detector does not raise a second alert for the same window, but the governance section has no page that reads those rows today. Over a webhook: this is the one place an alert reaches you. Every fire is POSTed to each webhook destination on the rule, with the payload above. A rule with no destinations records the alert and sends no request. Not in the OCSF export. The OCSF export carries governance events, and no exported record is linked to an anomaly alert. Do not build a SIEM rule that waits for one. Point a webhook destination at your SIEM instead. CLI: langwatch governance status reports hasAnomalyRules: true once at least one rule exists. That confirms a rule is defined, not that one has fired. End to end, for an organization that already has a rule:
  1. Start from a spend_spike rule scoped to your test IngestionSource with minBaselineUsd: 0.001 (tiny floor) and ratioVsBaseline: 1.5.
  2. Send one OTel event with gen_ai.usage.cost_usd: 0.05 (baseline seed), see otel-generic, Test it now.
  3. Wait long enough for the baseline window to roll past the seed (default windowSec: 86400 means 24h, so use the tighter values from the table above for fast iteration).
  4. Send a second event with gen_ai.usage.cost_usd: 2.00 (the spike).
  5. Watch your webhook receiver for the payload above. If the rule has no webhook destination, the alert is recorded and no request is sent.

What’s persisted vs what’s evaluated

A rule that is persisted but unevaluated is intentional: a rule can wait for the detector slice that will read it, so there is no rework when that slice ships. But it’s a real failure mode if you don’t know which is which. The two coverage tables above are the source of truth: only spend_spike + (organization | source_type | source) actually fires today. Everything else is preview state.
Last modified on October 12, 2026