---
title: "Outbound Integrations"
description: "Forward Incident Management events to Slack, Microsoft Teams, Discord, Jira, PagerDuty, Opsgenie or a generic webhook, and inspect or retry deliveries."
---

`GET /api/im/outbound` · `POST /api/im/outbound` · `GET /api/im/outbound/:id` · `PATCH /api/im/outbound/:id` · `DELETE /api/im/outbound/:id` · `GET /api/im/outbound/:id/history` · `POST /api/im/outbound/:id/retry`

An **outbound integration** forwards incident events (created, acknowledged, severity or status changed, comment, resolved) to an external system. Unlike a [channel](/api/incident-management/channels), which is a paging target of an escalation, an outbound integration mirrors the incident lifecycle into another tool. An integration is either org-wide (`teamId: null`) or belongs to one [team](/api/incident-management/teams).

## 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 (list, detail, history) is open to any IM-eligible role. **Creating, updating, deleting and retrying** require the `admin` role for an org-wide integration, and the `admin` role or team-admin membership for a team integration. An organization-wide API token runs with the role of the user who created it, so a token created by an organization admin passes this bar. Team-admin membership never applies to a token.

## Integration types and `config`

| `type` | Required `config` fields | Optional `config` fields | Write-only fields |
|--------|--------------------------|--------------------------|-------------------|
| `slack` | `webhookUrl` | | `webhookUrl` |
| `teams` | `webhookUrl` | | `webhookUrl` |
| `discord` | `webhookUrl` | | `webhookUrl` |
| `jira` | `baseUrl` (must start with `https://`), `email`, `apiToken`, `projectKey` | `issueType` (default `Task`) | `apiToken` |
| `pagerduty_v2` | `routingKey` | | `routingKey` |
| `opsgenie` | `apiKey` | `region` (`us` or `eu`, default `eu`) | `apiKey` |
| `webhook` | `webhookUrl` | `bearerToken`, `headers` (object of header name to value) | `webhookUrl`, `bearerToken`, `headers` |

**Write-only fields are encrypted at rest and never returned.** Every response sets them to `null` and adds a flag per field: `hasWebhookUrl`, `hasApiToken`, `hasRoutingKey`, `hasApiKey`, `hasBearerToken`, and for `webhook` also `hasHeaders` (the whole `headers` object is returned as `null`).

URLs are not checked for reachability when you save them. Customer-supplied destinations are checked at send time: a URL that resolves to a private or local address fails the delivery, which then shows up as `failed` in the [delivery history](#delivery-history).

## The `filters` object

| Field | Type | Description |
|-------|------|-------------|
| `teams` | integer[] | Forward only events of incidents of these team IDs. |
| `severities` | string[] | Subset of `sev1`, `sev2`, `sev3`, `sev4`. |
| `event_kinds` | string[] | Subset of `incident_created`, `acknowledged`, `severity_changed`, `status_changed`, `comment`, `resolved`. |

An empty object `{}` forwards everything. For `jira`, an omitted `event_kinds` defaults to `["incident_created"]`, so Jira gets one issue per incident.

## List integrations

`GET /api/im/outbound`

Returns all integrations of your organization, sorted by name, secrets redacted.

### Example (cURL)

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

### Response

`200 OK`

```json
[
  {
    "id": 6,
    "organizationId": 1,
    "teamId": null,
    "type": "pagerduty_v2",
    "name": "PagerDuty bridge",
    "config": { "routingKey": null, "hasRoutingKey": true },
    "filters": { "severities": ["sev1", "sev2"] },
    "isActive": true,
    "createdAt": "2026-09-22T12:00:00.000Z",
    "updatedAt": "2026-09-22T12:00:00.000Z"
  }
]
```

## Get an integration

`GET /api/im/outbound/:id`

Returns one integration, same shape as a list item.

## Create an integration

`POST /api/im/outbound`

### Request Body

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `type` | string | Yes | One of the types above. Cannot be changed later. |
| `name` | string | Yes | Trimmed, max 200 characters. |
| `config` | object | Yes | See [integration types and `config`](#integration-types-and-config). |
| `teamId` | integer \| null | No | Team of your organization, or `null`/omitted for an org-wide integration. |
| `filters` | object | No | See [the `filters` object](#the-filters-object). Default `{}`. |

New integrations are always active.

### Example (cURL)

```bash
curl -X POST "$BASE_URL/api/im/outbound" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "jira",
    "name": "Jira OPS",
    "teamId": 3,
    "config": {
      "baseUrl": "https://example.atlassian.net",
      "email": "ops-bot@example.com",
      "apiToken": "ATATT3xFfGF0aB12",
      "projectKey": "OPS"
    }
  }'
```

### Response

`200 OK`: the created integration, secrets redacted.

```json
{
  "id": 7,
  "organizationId": 1,
  "teamId": 3,
  "type": "jira",
  "name": "Jira OPS",
  "config": {
    "baseUrl": "https://example.atlassian.net",
    "email": "ops-bot@example.com",
    "apiToken": null,
    "projectKey": "OPS",
    "hasApiToken": true
  },
  "filters": { "event_kinds": ["incident_created"] },
  "isActive": true,
  "createdAt": "2026-09-22T12:10:00.000Z",
  "updatedAt": "2026-09-22T12:10:00.000Z"
}
```

## Update an integration

`PATCH /api/im/outbound/:id`

Partial update of `name`, `teamId`, `filters`, `config` and `isActive` (boolean, or the strings `"true"`/`"false"`). Other keys, including `type`, are ignored. You need the write bar on the integration's current scope, and, when you change `teamId`, also on the new scope (`null` makes it org-wide).

- `filters` replaces the stored filters completely.
- `config` is merged onto the stored config, then the result is validated against the type. Omit a write-only field, or send it as `null` or `""`, to keep the stored value.
- For `webhook`, sending `headers` replaces all stored headers, including their values. Omit `headers` to keep them.

```bash
curl -X PATCH "$BASE_URL/api/im/outbound/7" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "isActive": false }'
```

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

## Delete an integration

`DELETE /api/im/outbound/:id`

Also deletes the integration's delivery history.

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

`200 OK`

```json
{ "ok": true }
```

## Delivery history

`GET /api/im/outbound/:id/history`

Delivery attempts of one integration, newest first. Deliveries are kept for 90 days.

### Query Parameters

| Parameter | Type | Description |
|-----------|------|-------------|
| `limit` | integer | Page size, default `50`, clamped to 1 to 200. |
| `before` | integer | Cursor: return deliveries with an `id` lower than this. Pass the previous page's `nextCursor`. |

```bash
curl -X GET "$BASE_URL/api/im/outbound/6/history?limit=20" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/json"
```

`200 OK`. `requestSummary` holds only the type, the target origin (no path) and the method; `responseSummary` holds the status code and a truncated body. Neither contains secrets.

```json
{
  "deliveries": [
    {
      "id": 88412,
      "integrationId": 6,
      "incidentId": 42,
      "eventKind": "incident_created",
      "status": "failed",
      "requestSummary": { "type": "pagerduty_v2", "url": "https://events.pagerduty.com", "method": "POST" },
      "responseSummary": { "statusCode": 429, "body": "Rate limit exceeded" },
      "at": "2026-09-22T13:02:11.000Z"
    }
  ],
  "nextCursor": null,
  "hasMore": false
}
```

`status` is `sent`, `failed` or `retrying`.

## Retry a delivery

`POST /api/im/outbound/:id/retry`

Queues a new send for one delivery of this integration. The retry sends the **current** state of the incident (title, severity, status as of now), not the original payload. The call returns as soon as the job is queued; the outcome appears as a new entry in the delivery history.

Limited to 10 retries per minute per integration. The limit counts every authorized call, also one that then fails validation.

| Field | Type | Required | Description |
|-------|------|----------|-------------|
| `deliveryId` | integer | Yes | `id` of a delivery from this integration's history. |

```bash
curl -X POST "$BASE_URL/api/im/outbound/6/retry" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "deliveryId": 88412 }'
```

`200 OK`

```json
{ "ok": 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` (`imOutboundWriteDenied`) when writing an org-wide integration without the `admin` role
- `403 Forbidden` (`imTeamWriteDenied`) when writing a team integration without the write bar on that team
- `400 Bad Request` (`invalidRequestBody`) when `:id` is not a positive integer, `name` or `type` is missing or invalid, a required `config` field is missing, `jira`'s `baseUrl` is not `https`, a `filters` entry is invalid, `teamId` or `isActive` is invalid, or `deliveryId` is missing
- `404 Not Found` (`imOutboundNotFound`) when the integration does not exist, or belongs to another organization
- `404 Not Found` (`imOutboundDeliveryNotFound`) when `deliveryId` is not a delivery of this integration
- `422 Unprocessable Entity` (`invalidTeamId`) when `teamId` does not belong to your organization
- `422 Unprocessable Entity` (`imOutboundNotRetryable`) when the integration's type cannot be retried
- `429 Too Many Requests` (`imOutboundRetryRateLimited`) when the retry limit is reached. `data.retryAfter` is the wait in seconds
