# Webhook notifications

A webhook channel posts each notification to your endpoint as JSON. [Notification channels](/planning/notification-channels) owns adding the channel and choosing what goes to it; this page describes what your endpoint receives.

## Before you start

<PlanAvailability feature="WebhookChannel" plan="Starter" />

- Your endpoint is reached at a public address and answers within 10 seconds.

## The request

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

```json
{
  "event_type": "budget_alert",
  "schema_version": 1,
  "subject": "Budget 'Platform' reached 80% threshold",
  "content": "Current spend: EUR 825.00 of EUR 1,000.00",
  "html_content": "<p>Current spend: <b>EUR 825.00</b> of EUR 1,000.00</p>",
  "timestamp": "2026-10-12T09:00:00.0000000Z",
  "app_id": "app_Xk2pQ9vLr4mN8tY1wZ3aBc"
}
```

| Field | Meaning |
|---|---|
| `event_type` | What the notification is about; see the table below. Branch on it rather than on the subject. |
| `schema_version` | The version of this body. It is `1`, and rises only when a field changes meaning or goes away; a new field or event type does not raise it. |
| `subject`, `content`, `html_content` | The notification as a person reads it, in plain text and in HTML. The wording can change. |
| `timestamp` | When this attempt was sent, in UTC. |
| `app_id` | The channel the notification was sent to. |

The request carries two headers:

| Header | Value |
|---|---|
| `X-Costfluent-Event` | The `event_type`, so a router can dispatch without reading the body. |
| `X-Costfluent-Delivery` | The same on every attempt at one notification, so your endpoint can drop a repeat. A channel test has none. |

## Event types

| `event_type` | Sent when |
|---|---|
| `budget_alert` | A budget crosses one of its thresholds. |
| `cost_alert` | A spend threshold, unusual change or tag coverage alert fires. |
| `anomaly` | A cost anomaly is detected in a workspace whose anomaly notifications name the channel. |
| `channel_test` | Someone selects **Test** on the channel. |

## How it behaves

- Any `2xx` answer is a delivery. Another status, a redirect, or no answer within 10 seconds is a failed attempt, retried as [Notification channels](/planning/notification-channels) describes.
- A redirect is never followed.
- Scheduled cost reports are delivered by email only, so no webhook receives them.

## Related

- [Notification channels](/planning/notification-channels)
- [Alerts](/planning/alerts)
- [Public API versioning](/api/versioning)
