# Channels

> Supported notification channels (Slack, Discord, Teams, Telegram, email, SMS, push, PagerDuty, Opsgenie, webhooks, SSE), their configuration, message length limits, and priority mapping.

Source: https://docs.alphorn.dev/channels/

Channels are the destinations where Alphorn delivers notifications. Each channel connects to an external service - a Slack workspace, an email address, a push notification service, etc.

## Supported Channels

### Chat & Messaging

| Channel | Configuration |
|---|---|
| **Slack** | Incoming Webhook URL |
| **Discord** | Webhook URL |
| **Microsoft Teams** | Incoming Webhook URL |
| **Telegram** | Bot token + chat ID |
| **Google Chat** | Webhook URL |
| **Matrix** | Homeserver URL + access token + room ID |
| **Mattermost** | Incoming Webhook URL |
| **Rocket.Chat** | Incoming Webhook URL |
| **Zulip** | Server URL + bot email + API key + stream + topic |

### Email

| Channel | Configuration |
|---|---|
| **Email (SMTP)** | SMTP host, port, encryption, username, password, from address, to address |
| **SendGrid** | API key + from email (+ optional from name) + to email |
| **Mailgun** | API key + domain + region (US/EU) + from address + to address |

### Push Notifications

| Channel | Configuration |
|---|---|
| **Ntfy** | Server URL + topic (+ optional access token) |
| **Pushover** | API token + user key |
| **Gotify** | Server URL + app token |

### SMS

| Channel | Configuration |
|---|---|
| **Twilio SMS** | Account SID + auth token + from number + to number |
| **Vonage SMS** | API key + API secret + from + to number |

### Incident Management

| Channel | Configuration |
|---|---|
| **PagerDuty** | Routing key (Events API v2 integration key) + default severity |
| **Opsgenie** | API key + region (US/EU) + default priority |

### Custom Integrations

| Channel | Configuration |
|---|---|
| **Webhook** | Any HTTP endpoint URL, method, custom headers |
| **Live Stream (SSE)** | Output format (JSON/text) + which fields to include. See [Streaming](/streaming) |

## Adding a Channel

1. Navigate to **Channels** in the dashboard
2. Click **Add channel**
3. Select the channel type
4. Enter the required configuration - each channel type shows a setup guide in the form
5. Send a test notification to verify (admins and owners; not available for Live Stream)
6. Link the channel to a webhook - on the webhook page, select it in the channel list

A channel only receives notifications from webhooks it is linked to. You can link one channel to many webhooks, each with its own [filter](/filtering).

<Callout type="info">
Channel URLs (webhook URLs, server URLs, homeserver URLs) can't point at the Alphorn instance itself. This prevents notification loops. Discord channels only accept Discord webhook URLs (`https://discord.com/api/webhooks/...`).
</Callout>

## Disabling and Deleting Channels

- A **disabled** channel receives no new notifications, and pending retries for it are dropped. It still counts toward your plan's channel limit.
- **Deleting** a channel removes it from all webhooks. Its delivery history stays visible under **Messages**, and it no longer counts toward your plan's channel limit.

## Send Rate Limits

To stay within the services' own limits, Alphorn sends at most 60 messages per minute to a Slack channel, 50 to a Discord channel, and 20 to a Telegram channel. Extra deliveries wait for the next minute without using up a retry - see [Retries → Per-Channel Throttling](/retries#per-channel-throttling).

## Message Length Limits

Incoming titles are cut to 200 characters and messages to 4,000 characters. Some services have lower limits, so Alphorn shortens the text to fit each one before sending (longer text ends with `…`):

| Channel | Limit |
|---|---|
| Slack | Title 150, message 3,000 |
| Discord | Title 256, message 4,096 |
| Telegram | Title 256, title + message 4,096 |
| Google Chat | Message 4,096 |
| Pushover | Title 250, message 1,024 |
| PagerDuty | Summary 1,024 |
| Opsgenie | Message (title) 130, description 15,000 |
| Twilio SMS | 1,600 |

Email and Opsgenie need a subject or title line: they use the notification title, or the first line of the message (up to 100 characters) when there is no title. PagerDuty gets `Title: message` as its summary, cut to 1,024 characters.

## Priority Mapping

Channels with their own priority or severity levels get the notification priority mapped onto their scale:

| Alphorn priority | ntfy | Gotify | Pushover | PagerDuty | Opsgenie |
|---|---|---|---|---|---|
| 1 (Min) | min | 1 | -1 | info | P5 |
| 2 (Low) | low | 3 | -1 | info | P4 |
| 3 (Default) | default | 5 | 0 | warning | P3 |
| 4 (High) | high | 8 | 1 | error | P2 |
| 5 (Urgent) | urgent | 10 | 2 | critical | P1 |

Notifications without a priority use the channel's default: `default` for ntfy, 5 for Gotify, 0 for Pushover, and the **Default Severity** / **Default Priority** you configured for PagerDuty and Opsgenie.

## Channel Limits

Channel availability depends on your plan:

| Plan | Channels |
|---|---|
| Free | 3 channels |
| Pro | Unlimited |
| Business | Unlimited |

## Generic Webhook

The **Webhook** channel lets you send notifications to any HTTP endpoint. Use it to integrate with services that Alphorn doesn't have a dedicated channel for.

| Setting | Description |
|---|---|
| Endpoint URL | Where to send the request |
| HTTP Method | `POST` (default) or `PUT` |
| Custom Headers | Extra headers, e.g. `Authorization` for the receiving service. Invalid header names or values are rejected when you save |

Alphorn sends a JSON body:

```json
{
  "title": "Notification title",
  "message": "Notification body",
  "priority": 3,
  "tags": ["tag1", "tag2"],
  "payload": { "the": "original JSON request body" },
  "timestamp": "2025-01-15T10:30:00.000Z"
}
```

- `message` and `timestamp` are always present
- `title`, `priority`, and `tags` are left out when the notification doesn't have them
- `payload` is the **original JSON request body** that came into the Alphorn webhook, nested under its own key. For plain-text notifications it is an empty object (`{}`)
- The test button sends a different body - `{"title", "body", "timestamp", "_test": true}` - so your receiver can tell test pings apart

The request also carries a signed `X-Alphorn-Trace` header (other URL-based channels like Slack, Teams, Matrix, or ntfy send it too). Alphorn uses it to detect loops when a channel points at another Alphorn webhook; after 3 hops (`WEBHOOK_MAX_HOPS` when self-hosting), or when the notification comes back to a webhook it already passed, the request is rejected with `508`. The header is signed with `BETTER_AUTH_SECRET`, so loops across two self-hosted instances are only detected when both use the same secret.

<Callout type="info">
A Webhook channel can't point at the same Alphorn instance it runs on. To forward notifications between your own webhooks, link the channels directly to the webhook instead.
</Callout>
