# Integrations (/docs/concepts/integrations)



A **destination** is where [alerts](/docs/concepts/alerts) go. Connect destinations in the
dashboard under Settings → **Alerts & integrations**, or with a key that has `credentials:write`.
A rule notifies every enabled destination by default, or only the ones it lists.

```bash
curl https://api.gridrouter.io/v1/alerts/destinations \
  -H "Authorization: Bearer $GRID_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "On-call",
    "min_severity": "critical",
    "config": { "type": "pagerduty", "region": "us" },
    "secret": "<your 32-character integration key>"
  }'
```

Every destination has a `name`, `enabled`, a `min_severity` (alerts below it aren't sent there,
default `info`) and `notify_on_resolve` (default on). The `secret` (a webhook URL, routing key or
API key) is stored encrypted in your workspace's vault and is never returned: reads show a masked
hint such as `https://hooks.slack.com/…abcd` or `…abcd`. Send a new `secret` with
`PUT /v1/alerts/destinations/{id}` to replace it. A workspace can have up to 20 destinations.

## Lifecycle [#lifecycle]

What each destination receives as an alert moves through its [lifecycle](/docs/concepts/alerts#lifecycle):

| Alert        | Email                                  | Webhook           | Slack, Discord, Teams | PagerDuty                     | Opsgenie        |
| ------------ | -------------------------------------- | ----------------- | --------------------- | ----------------------------- | --------------- |
| Triggered    | An email, or a line in the next digest | `alert.triggered` | A message             | `trigger` (opens an incident) | Create an alert |
| Acknowledged | Nothing                                | Nothing           | Nothing               | `acknowledge`                 | Acknowledge     |
| Resolved     | An email, or a digest line             | `alert.resolved`  | A "Resolved" message  | `resolve`                     | Close           |

Resolutions are sent only when the destination's `notify_on_resolve` is on. Alerts from a
[muted](/docs/concepts/alerts#mute) rule send nothing.

## Email [#email]

Configure `{ "type": "email", "recipients": ["oncall@example.com"], "digest": "immediate" }` with
1 to 20 recipients. Email takes no secret.

* **`immediate`** sends one email per trigger and resolution. The subject is
  `[GridRouter] <Severity or Resolved>: <alert title>`, and the body has the alert's message, the
  rule, severity, status, value, threshold, subject, start time, how often it repeated and a link
  to the alert.
* **`hourly`** or **`daily`** collects alerts into one digest per hour or day, sent at the first
  hour or day boundary (UTC) after the oldest alert in it, with up to 200 alerts.

Email is sent once and not retried, so a partial failure never repeats to recipients who already
got it.

## Webhooks [#webhooks]

Connect one of your [webhook endpoints](/docs/concepts/webhooks) with
`{ "type": "webhook", "endpoint_id": "whe_…" }`. The rules routed to this destination send their
alerts to that endpoint as signed `alert.triggered` and `alert.resolved` events, even if the
endpoint doesn't subscribe to those types. Deliveries follow the webhook
[retry schedule](/docs/concepts/webhooks#retries) and show in its delivery log. The endpoint's
signing secret is its own; this destination takes no secret.

## Slack [#slack]

Alerts post to a channel through a Slack **incoming webhook**.

<Steps>
  <Step>
    Go to [api.slack.com/apps](https://api.slack.com/apps) and **Create New App** → **From scratch**.
    Name it (for example "GridRouter alerts") and pick your workspace.
  </Step>

  <Step>
    Open **Incoming Webhooks** and turn on **Activate Incoming Webhooks**.
  </Step>

  <Step>
    Click **Add New Webhook to Workspace**, choose the channel and click **Allow**.
  </Step>

  <Step>
    Copy the **Webhook URL**. It starts with `https://hooks.slack.com/services/`. Paste it as the
    destination's secret.
  </Step>
</Steps>

The message uses Block Kit: a header such as "Warning: Error-rate spike", the alert's message, a
field for each fact (rule, severity, status, value, threshold, subject, start time, occurrences),
an **Open in GridRouter** button and the alert id. Text is sent as plain text, so nothing in an
alert can mention a user or channel. The webhook URL decides the channel; the optional `channel`
setting is a label for your reference.

## Discord [#discord]

<Steps>
  <Step>
    In your server, open **Server Settings** → **Integrations** → **Webhooks**.
  </Step>

  <Step>
    Click **New Webhook**, name it, and choose the channel.
  </Step>

  <Step>
    Click **Copy Webhook URL**. It starts with `https://discord.com/api/webhooks/`. Paste it as the
    destination's secret.
  </Step>
</Steps>

The message is an embed from "GridRouter": the title links to the alert, the color follows the
severity (green once resolved), and each fact is an inline field. Mentions are disabled, so an
alert can never ping `@everyone` or a role.

## Microsoft Teams [#microsoft-teams]

Teams alerts go through a **Workflows** webhook. The older Office 365 connectors (incoming webhook
connectors) are retired; use Workflows.

<Steps>
  <Step>
    In Teams, open the channel, click **•••** next to its name and choose **Workflows**.
  </Step>

  <Step>
    Pick the template **Post to a channel when a webhook request is received**, name the workflow,
    and confirm the team and channel.
  </Step>

  <Step>
    Copy the URL the workflow shows, for example
    `https://prod-00.westus.logic.azure.com/workflows/…`. Paste it as the destination's secret.
  </Step>
</Steps>

The message is an Adaptive Card (version 1.4): a bold title colored by severity, the alert's
message, a fact set and an **Open in GridRouter** action. Workflows URLs on `*.logic.azure.com`,
`*.environment.api.powerplatform.com` and `*.webhook.office.com` are accepted.

## PagerDuty [#pagerduty]

GridRouter uses the PagerDuty **Events API v2**.

<Steps>
  <Step>
    In PagerDuty, open the **Service** that should be paged and go to its **Integrations** tab.
  </Step>

  <Step>
    Click **Add integration** (or **Add another integration**), choose **Events API V2** and add it.
  </Step>

  <Step>
    Copy the **Integration Key**: 32 letters and digits. Paste it as the destination's secret.
  </Step>

  <Step>
    If your PagerDuty account is in the EU service region, set `"region": "eu"` (events go to
    `events.eu.pagerduty.com`); otherwise leave the default `us`.
  </Step>
</Steps>

Each GridRouter alert is one PagerDuty incident: the alert id is the `dedup_key`, so trigger,
acknowledge and resolve all land on the same incident, and repeats are deduplicated. The event's
summary is the alert's title and message, `source` is `gridrouter`, `component` is the alert's
subject (or `workspace`), `group` is the metric, `class` is the rule name, and the facts are in
`custom_details`, with a link back to the alert.

## Opsgenie [#opsgenie]

GridRouter uses the Opsgenie **Alert API**.

<Steps>
  <Step>
    In Opsgenie, go to **Settings** → **Integrations** and add an **API** integration. Name it and
    assign the team that should be alerted.
  </Step>

  <Step>
    Copy the integration's **API key**, and save the integration with it turned on. Paste the key as
    the destination's secret.
  </Step>

  <Step>
    If your Opsgenie account is in the EU region, set `"region": "eu"` (requests go to
    `api.eu.opsgenie.com`); otherwise leave the default `us`.
  </Step>
</Steps>

Each GridRouter alert is one Opsgenie alert with the GridRouter alert id as its `alias`, so
acknowledging and resolving in GridRouter acknowledge and close it by alias. The Opsgenie alert's
message is the title, the description has the message and a link, `source` is `GridRouter`,
`entity` is the subject (or `workspace`), the tags are `gridrouter`, the metric and the severity,
and the facts are in `details`.

## Severity mapping [#severity-mapping]

GridRouter severities map to PagerDuty severities and Opsgenie priorities. These are the defaults;
change them per destination with `severity_map` (PagerDuty: `critical`, `error`, `warning`,
`info`) or `priority_map` (Opsgenie: `P1` to `P5`).

| GridRouter severity | PagerDuty severity | Opsgenie priority |
| --- | --- | --- |
| `critical` | `critical` | `P1` |
| `warning` | `warning` | `P3` |
| `info` | `info` | `P5` |

```json
{ "type": "opsgenie", "region": "eu", "priority_map": { "critical": "P1", "warning": "P2", "info": "P5" } }
```

## Test a destination [#test-a-destination]

**Send test** in the dashboard, or `POST /v1/alerts/destinations/{id}/test`, delivers "Test alert
from GridRouter" right away and returns `ok`, the HTTP `status`, `latency_ms` and any `error`.
PagerDuty and Opsgenie tests open and immediately resolve their own incident, so nobody is left
paged. Test emails are queued for the mailer, and the result says `queued: true`. To test every
destination a rule notifies at once, use `POST /v1/alerts/rules/{id}/test`.

## Delivery [#delivery]

* Slack, Discord, Teams, PagerDuty and Opsgenie notifications are retried up to 6 attempts, with
  backoff from 30 seconds, on `408`, `409`, `425`, `429`, `5xx` and network errors. Other errors
  are final.
* Each result is recorded on the alert's timeline ("sent to" or "failed") and in the destination's
  health: status, deliveries, failures, consecutive failures and the last error.
* Every URL passes the same checks as [webhook endpoints](/docs/concepts/webhooks#url-rules) when
  saved and before each send, and must be on that service's own hosts: `hooks.slack.com` for
  Slack, `discord.com` for Discord and the Workflows hosts above for Teams. Redirects are never
  followed.

