> ## Documentation Index
> Fetch the complete documentation index at: https://www.truefoundry.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhook Notification Channel

> Send TrueFoundry budget alerts to your own HTTPS endpoint as structured JSON, with optional bearer or basic authentication.

A webhook notification channel posts a JSON payload to an HTTPS endpoint you control whenever TrueFoundry raises a notification. Use it to feed budget alerts into your own systems, for example to route a message to a specific person through a relay service.

## Add a webhook notification channel

**1. Open the integrations page**

Go to **Integrations** in the TrueFoundry UI and click **Add Integration Provider**.

**2. Select Custom**

Choose **Custom** as the provider type and give the provider account a name.

**3. Add the integration**

Add an integration, choose **Webhook Notification Channel**, and fill in the fields below.

**4. Save**

Click **Add Integration Provider** to save.

### Fields

| Field | Required | Description |
| - | - | - |
| **Display Name** | Yes | A unique name for the integration, for example `budget-webhook`. |
| **Webhook URL** | Yes | The HTTPS endpoint that receives the POST request. Plain `http://` URLs are rejected. |
| **Auth Data** | No | Credentials sent in the `Authorization` header of every request. Choose **Bearer** (`Authorization: Bearer <token>`) or **Basic** (`Authorization: Basic <base64(username:password)>`). Secrets are stored as secret references. |
| **Access Control** | No | Users or groups allowed to use this channel. |

> [!NOTE]
>
> The endpoint must be reachable from the TrueFoundry control plane. When SSRF protection is enabled on the control plane, URLs that resolve to private or internal network addresses are blocked.

## Use the channel

Select **Webhook** as the notification target of a [budget alert](https://www.truefoundry.com/docs/ai-gateway/budget-limiting-v2.md#budget-milestone-alerts) and pick the integration from the **Notification Channel** dropdown. In a budget, `notification_target` is a list under `alerts`:

```yaml theme={"dark"}
alerts:
  thresholds: [90, 100]
  notification_target:
    - type: webhook
      notification_channel: truefoundry:custom:my-hooks:notification-channel:my-webhook
```

Replace the `notification_channel` value with the FQN of your integration.

## Request format

Every notification is sent as `POST` with `Content-Type: application/json`.

| Field | Description |
| - | - |
| `id` | Unique ID of the notification. Use it to deduplicate. |
| `type` | Event type. Use it to decide how to parse `data`. See [Event types](#event-types). `notification` is used when the sender has no structured data. |
| `version` | Payload version. Currently `v1`. |
| `tenant` | Name of your TrueFoundry tenant. |
| `created_at` | Time the notification was created, in ISO 8601 UTC. |
| `subject` | Human-readable title. |
| `body` | Human-readable message. |
| `data` | Structured, event-specific fields. Omitted when `type` is `notification`. |

## Event types

The `type` field tells you which shape `data` has.

| `type` | Sent when |
| - | - |
| [`budget.threshold_crossed`](#budget-threshold-crossed) | Usage crosses a budget alert threshold. |
| [`test`](#test-notification) | You test the channel. `data` is empty. |

### Budget threshold crossed

Sent as `type: "budget.threshold_crossed"` when usage crosses an alert threshold.

```json theme={"dark"}
{
  "id": "0b6f7c1e-5d0a-4a52-9a0d-3f6c1f4b2a10",
  "type": "budget.threshold_crossed",
  "version": "v1",
  "tenant": "acme",
  "created_at": "2026-10-22T06:00:00.000Z",
  "subject": "Budget Alert: eng-daily (for jane@acme.com) - 90% threshold crossed",
  "body": "Budget \"eng-daily (for jane@acme.com)\" has crossed the 90% usage threshold.\n\nCurrent Usage: 91.20%\nBudget Limit: $10.0000 per day\nPeriod: Day\n\nPlease review your budget configuration and usage to avoid exceeding your limits.",
  "data": {
    "budget": {
      "name": "eng-daily",
      "type": "team-budget-config",
      "team_name": "eng",
      "mode": "enforce",
      "applies_to": "per-user"
    },
    "entity": { "dimension": "user", "value": "jane@acme.com" },
    "period": "day",
    "window_start": "2026-10-22T00:00:00.000Z",
    "threshold_percent": 90,
    "usage_percent": 91.2,
    "usage_usd": 9.12,
    "limit_usd": 10,
    "remaining_usd": 0.88
  }
}
```

| `data` field | Description |
| - | - |
| `budget.name` | Name of the budget. |
| `budget.type` | `tenant-budget-config` or `team-budget-config`. |
| `budget.team_name` | Owning team for team budgets, otherwise `null`. |
| `budget.mode` | `enforce`, `audit`, or `soft_enforce`. |
| `budget.applies_to` | How the limit is partitioned: `aggregate`, `per-user`, `per-model`, `per-virtual-account`, `per-agent`, or `metadata`. |
| `entity` | The user, model, virtual account, agent, or metadata value the alert is for. `null` for `aggregate` budgets. |
| `period` | `day`, `week`, `month`, `quarter`, or `lifetime`. |
| `window_start` | Start of the current budget window. `null` for `lifetime` budgets. |
| `threshold_percent` | The alert threshold that was crossed. |
| `usage_percent` | Usage as a percentage of the limit, capped at 100. |
| `usage_usd` | Spend in the current window in USD. Can exceed `limit_usd`. |
| `limit_usd` | Limit for the current window in USD, after any per-entity override. |
| `remaining_usd` | `limit_usd` minus `usage_usd`, never below 0. |

### Test notification

The channel test sends `type: "test"` with an empty `data` object.

## Delivery and retries

* A `2xx` response marks the notification as delivered. Any other response, a timeout (10 seconds), or a connection error fails the attempt, and TrueFoundry retries it, up to 3 attempts in total.
* Delivery is at-least-once. Your endpoint can receive the same notification more than once, so deduplicate on `id`.
* Respond quickly and process asynchronously. Slow responses are treated as failures.

## Example: notify a user through a relay

Budget alerts name the breaching user in `data.entity.value`. A small service can use it to message that person, for example with Slack's `users.lookupByEmail` and `chat.postMessage`:

```python theme={"dark"}
from flask import Flask, request
from slack_sdk import WebClient

app = Flask(__name__)
slack = WebClient(token="<slack-bot-token>")
seen_ids = set()  # use a shared store such as Redis in production


@app.post("/tfy/budget")
def budget_alert():
    event = request.get_json()
    if event["id"] in seen_ids:
        return "", 200
    seen_ids.add(event["id"])

    entity = event["data"]["entity"]
    if event["type"] == "budget.threshold_crossed" and entity and entity["dimension"] == "user":
        user = slack.users_lookupByEmail(email=entity["value"])["user"]["id"]
        slack.chat_postMessage(channel=user, text=event["body"])
    return "", 200
```

> [!NOTE]
>
> For Slack direct messages without a relay, you can also use the [Slack Bot](https://www.truefoundry.com/docs/slack-bot-integration.md) target with **Slack User Emails**.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.