---
title: Alert channels
description: Where alerts go. Slack, Discord, Telegram, Microsoft Teams, PagerDuty, email, and signed webhooks; setup, testing, and delivery behaviour.
section: alerts
order: 1
---

# Alert channels

A channel is a team-scoped destination for alerts. Create as many as you need; every alert AgentPing raises fans out to every enabled channel on the team. The one exception is [spend budgets](/docs/spend/alerts), which deliver to the single channel attached to the budget.

Configure channels in the dashboard under **Team settings, Alerts**. Each channel has a label, an enabled toggle, and a **Test** button that sends a `channel.test` event so you can verify the wiring before anything is on fire.

## Channel types

| Type | You provide | Notes |
|------|-------------|-------|
| Email | An email address | Available on every tier, including Free. |
| Slack | An incoming webhook URL (`hooks.slack.com/...`) | Create one in Slack under Apps, Incoming Webhooks. |
| Discord | An incoming webhook URL (`discord.com/api/webhooks/...`) | From the channel's Edit, Integrations, Webhooks menu. |
| Telegram | A tap on the connect link we generate | Opens the AgentPing bot; tap Start to link a chat or group. |
| Microsoft Teams | An incoming webhook URL (`*.webhook.office.com`) | From the channel's Connectors menu. |
| PagerDuty | An Events API v2 integration key, plus a severity | Severity is `critical`, `error`, `warning` (default), or `info`. |
| Webhook | Any HTTPS URL, plus an optional signing secret | POST, JSON body. Secret must be at least 16 characters. |

## Tier availability

| Tier | Channels |
|------|----------|
| Free | Email |
| Starter | Email, Slack, Discord, Telegram, webhook |
| Team and above | Email, Slack, Discord, Telegram, webhook, PagerDuty, Microsoft Teams |

## What fires an alert

Every event type is documented on its own page:

| Event | Fired when | Configured in |
|-------|-----------|---------------|
| `agent.overdue` / `agent.recovered` | A scheduled agent misses its expected window, then comes back | [Heartbeat freshness](/docs/pulse/freshness) |
| `check.failing` / `check.recovered` | A check hits its fail streak, then passes again | [Verify alerts](/docs/verify/alerts) |
| `rubric.failing` / `rubric.recovered` | A judge score drops below the pass threshold for the streak, then recovers | [Verify alerts](/docs/verify/alerts) |
| `spend.anomaly` | An agent's daily spend breaks its own baseline | [Spend alerts](/docs/spend/alerts) |
| `spend.budget_threshold` | Month-to-date spend crosses a budget threshold | [Spend alerts](/docs/spend/alerts) |
| `channel.test` | You press Test | This page |

## Delivery and retries

Every delivery is attempted up to 3 times with a 30-second backoff and a 10-second request timeout. The channel list shows each channel's last successful delivery and, if the most recent attempt failed, when and why. Webhook attempts are additionally logged with HTTP status and duration.

## Webhook details

Webhook channels receive a `POST` with `Content-Type: application/json` and `User-Agent: AgentPing-Alerts/1.0`. The body always carries `event`, `subject`, `body` (a human-readable summary), and a `url` deep link into the dashboard; event-specific fields ride alongside.

```json
{
  "event": "agent.overdue",
  "subject": "nightly-report is overdue",
  "body": "No run received for 47 minutes past the expected window.",
  "agent": { "slug": "nightly-report", "id": "agt_..." },
  "run": null,
  "last_seen_at": "2026-06-10T03:00:12Z",
  "minutes_overdue": 47,
  "url": "https://agentping.io/agents/agt_..."
}
```

`check.failing` and `rubric.failing` events instead carry `failing_checks` or `failing_rubrics` arrays with the name, the detail or score and threshold, and the judge's reasoning where there is one.

### Verifying signatures

If you set a signing secret, every request includes:

```
X-AgentPing-Signature: sha256=<hex>
```

The value is an HMAC-SHA256 of the raw request body using your secret. Verify it before trusting the payload:

```python
import hashlib, hmac

expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
valid = hmac.compare_digest(expected, request.headers["X-AgentPing-Signature"])
```

Next: wire up [heartbeat freshness](/docs/pulse/freshness), the alert most teams want first.
