---
title: "Channels"
description: "List, create, update and delete Incident Management paging channels (webhook, Slack, email), including the write-only handling of webhook URLs and headers."
---

`GET /api/im/channels` · `POST /api/im/channels` · `GET /api/im/channels/:id` · `PATCH /api/im/channels/:id` · `DELETE /api/im/channels/:id`

A **channel** is a shared paging destination of your organization: a generic webhook, a Slack incoming webhook, or an email address. A channel is either org-wide (`teamId: null`) or belongs to one [team](/api/incident-management/teams). A channel can be set as the organization's fallback channel in the [organization settings](/api/incident-management/org-settings), the last resort when an escalation runs out of tiers.

## Authentication

Requires the base IM access every endpoint in this API needs (an IM-eligible role `admin`, `editor` or `responder`, or an organization-wide API token; Incident Management must be enabled for the organization). Reading is open to any IM-eligible role. **Writing** an org-wide channel requires the `admin` role. Writing a team channel requires the `admin` role or team-admin membership of that team. An organization-wide API token runs with the role of the user who created it, so a token created by an organization admin can write every channel. Team-admin membership never applies to a token.

## Channel types and `config`

| `type` | Required `config` fields | Optional `config` fields |
|--------|--------------------------|--------------------------|
| `webhook` | `webhookUrl` | `headers` (object of header name to value) |
| `slack` | `webhookUrl` | `headers` |
| `email` | `email` | |

- `webhookUrl` must be an `http` or `https` URL with a public hostname. Private, loopback and reserved IP addresses, `localhost`, `*.local`, `*.internal`, `home.arpa` and hostnames without a dot are rejected.
- `headers` must not set the `Host` header.
- `config` may be at most 8 KB as JSON. Other keys are stored as sent.

**`webhookUrl` and header values are write-only.** They are encrypted at rest and never returned. Every response replaces them: `webhookUrl` is `null` and `hasWebhookUrl` says whether one is stored; `headers` keeps the header names with `null` values and `hasHeaders` says whether any header is stored.

## List channels

`GET /api/im/channels`

Returns all channels of your organization (org-wide and team channels), sorted by name, with the team name resolved.

### Example (cURL)

```bash
curl -X GET "$BASE_URL/api/im/channels" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
```

### Response

`200 OK`

```json
[
  {
    "id": 9,
    "organizationId": 1,
    "teamId": 3,
    "teamName": "Platform Team",
    "type": "webhook",
    "name": "Ops webhook",
    "config": {
      "webhookUrl": null,
      "headers": { "X-Api-Key": null },
      "hasWebhookUrl": true,
      "hasHeaders": true
    },
    "isActive": true,
    "createdAt": "2026-09-20T10:00:00.000Z",
    "updatedAt": "2026-09-20T10:00:00.000Z"
  },
  {
    "id": 10,
    "organizationId": 1,
    "teamId": null,
    "teamName": null,
    "type": "email",
    "name": "NOC mailbox",
    "config": { "email": "noc@example.com" },
    "isActive": true,
    "createdAt": "2026-09-20T10:05:00.000Z",
    "updatedAt": "2026-09-20T10:05:00.000Z"
  }
]
```

## Get a channel

`GET /api/im/channels/:id`

Returns one channel (without `teamName`) plus a `references` block that tells you whether it is the organization's fallback channel. A fallback channel cannot be deleted or deactivated.

```json
{
  "id": 10,
  "organizationId": 1,
  "teamId": null,
  "type": "email",
  "name": "NOC mailbox",
  "config": { "email": "noc@example.com" },
  "isActive": true,
  "createdAt": "2026-09-20T10:05:00.000Z",
  "updatedAt": "2026-09-20T10:05:00.000Z",
  "references": {
    "isOrgFallback": true,
    "fallbackOrganizationIds": [1]
  }
}
```

## Create a channel

`POST /api/im/channels`

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | `webhook`, `slack` or `email`. |
| `name` | string | Yes | Display name, trimmed, max 120 characters. |
| `config` | object | Yes | See [channel types and `config`](#channel-types-and-config). |
| `teamId` | integer \| null | No | Team of your organization, or `null`/omitted for an org-wide channel. |
| `isActive` | boolean | No | Default `true`. Any value other than `true` creates an inactive channel. |

### Example (cURL)

```bash
curl -X POST "$BASE_URL/api/im/channels" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "webhook",
    "name": "Ops webhook",
    "teamId": 3,
    "config": {
      "webhookUrl": "https://hooks.example.com/im/7f3a",
      "headers": { "X-Api-Key": "k_live_51b2" }
    }
  }'
```

### Response

`200 OK`: the created channel, with secrets redacted as described above. The response never echoes the URL or header values you just sent.

## Update a channel

`PATCH /api/im/channels/:id`

Accepts only `name`, `type`, `config`, `isActive` and `teamId`; any other key, or an empty body, is a `400`. You need write access to the channel's current scope, and, when you change `teamId`, also to the new scope (moving a channel to org-wide needs the `admin` role).

`config` is merged onto the stored config, so you never have to resend secrets you cannot read back:

- Omit `webhookUrl`, or send it as `null` or `""`, to keep the stored URL. Send a new URL to replace it.
- If you send `headers`, it replaces the stored set of header names. A header whose value is `null` or `""` keeps its stored value; a header you leave out is removed.
- When you change `type`, the merged config is validated against the new type, e.g. switching an `email` channel to `webhook` requires a `webhookUrl`.
- Deactivating (`isActive: false`) the organization's fallback channel is refused with `409`.

```bash
curl -X PATCH "$BASE_URL/api/im/channels/9" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Ops webhook (primary)", "config": { "headers": { "X-Api-Key": null } } }'
```

`200 OK`: the updated channel, secrets redacted.

## Delete a channel

`DELETE /api/im/channels/:id`

Refused with `409` while the channel is the organization's fallback channel; point `fallbackChannelId` in the [organization settings](/api/incident-management/org-settings) elsewhere (or to `null`) first.

```bash
curl -X DELETE "$BASE_URL/api/im/channels/9" \
  -H "Authorization: Bearer $TOKEN"
```

`200 OK`

```json
{ "success": true }
```

## Common errors

- `401 Unauthorized` when not authenticated
- `403 Forbidden` (`imAccessDenied`) when using a customer-scoped token, or a session without an IM-eligible role
- `403 Forbidden` (`imNotEnabled`) when Incident Management is not enabled for the organization
- `403 Forbidden` (`imChannelWriteDenied`) when you may not write the channel's scope (or the target scope of a move)
- `400 Bad Request` (`invalidRequestBody`) when `:id` is not a positive integer, `type`, `name` or `config` is missing or invalid, `webhookUrl` or `email` fails validation, `config` exceeds 8 KB, `teamId` is not a team of your organization, `isActive` is not a boolean (update), or the update body is empty or contains unsupported keys
- `400 Bad Request` (`webhookHostHeaderForbidden`) when `config.headers` sets the `Host` header
- `404 Not Found` (`imChannelNotFound`) when the channel does not exist, or belongs to another organization
- `409 Conflict` (`imChannelReferencesExist`) when deleting the organization's fallback channel. `data.fallbackOrganizationIds` lists the referencing organization
- `409 Conflict` (`imChannelIsOrgFallback`) when deactivating the organization's fallback channel
