> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rootly.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Outgoing Webhooks

> Send real-time event notifications from Rootly to any external HTTP endpoint as incidents, alerts, pulses, and workflows progress through their lifecycles.

## Overview

Outgoing webhooks let you push Rootly events to any system that accepts HTTP POST requests — a custom internal tool, a data pipeline, an audit log, or a third-party service that does not have a native Rootly integration.

Each webhook endpoint you configure receives a JSON payload for the event types you subscribe it to. Rootly signs every delivery so your server can verify the payload came from Rootly.

## Create a Webhook Endpoint

<Steps>
  <Step title="Navigate to Webhooks">
    Go to **Settings → Webhooks** and click **New Webhook**.
  </Step>

  <Step title="Name and URL">
    Give the endpoint a descriptive name and enter the destination URL. The URL must be publicly reachable and accept HTTPS POST requests.

    <ParamField body="Name" type="string" required>
      A label for this endpoint, unique within your team.
    </ParamField>

    <ParamField body="URL" type="string" required>
      The HTTPS endpoint that will receive event payloads.
    </ParamField>
  </Step>

  <Step title="Select event types">
    Choose which events this endpoint should receive. If you leave the event types list empty, the endpoint will receive **all** events.

    See [Event Types](#event-types) below for the full list.
  </Step>

  <Step title="Save and copy the secret">
    Click **Save**. Rootly generates a signing secret for this endpoint automatically. Copy it now — it is only shown once. You will use it to verify incoming payloads on your server.

    <Warning>
      The signing secret cannot be retrieved after you leave this page. If you lose it, you can regenerate it from the endpoint's edit form, which will invalidate the old secret immediately.
    </Warning>
  </Step>
</Steps>

***

## Event Types

Subscribe an endpoint to one or more event types. An empty subscription list means the endpoint receives all events.

### Incidents

| Event                | When it fires                      |
| -------------------- | ---------------------------------- |
| `incident.created`   | A new incident is opened           |
| `incident.updated`   | Any field on the incident changes  |
| `incident.in_triage` | Incident moves to In Triage status |
| `incident.mitigated` | Incident is marked as mitigated    |
| `incident.resolved`  | Incident is resolved               |
| `incident.cancelled` | Incident is cancelled              |
| `incident.deleted`   | Incident is deleted                |

### Scheduled Incidents

| Event                            | When it fires                       |
| -------------------------------- | ----------------------------------- |
| `incident.scheduled.created`     | A scheduled incident is created     |
| `incident.scheduled.updated`     | A scheduled incident is updated     |
| `incident.scheduled.in_progress` | A scheduled incident becomes active |
| `incident.scheduled.completed`   | A scheduled incident completes      |
| `incident.scheduled.deleted`     | A scheduled incident is deleted     |

### Retrospectives

| Event                            | When it fires                |
| -------------------------------- | ---------------------------- |
| `incident_post_mortem.created`   | A retrospective is created   |
| `incident_post_mortem.updated`   | A retrospective is updated   |
| `incident_post_mortem.published` | A retrospective is published |
| `incident_post_mortem.deleted`   | A retrospective is deleted   |

### Status Page Events

| Event                                | When it fires                  |
| ------------------------------------ | ------------------------------ |
| `incident_status_page_event.created` | A status page event is created |
| `incident_status_page_event.updated` | A status page event is updated |
| `incident_status_page_event.deleted` | A status page event is deleted |

### Timeline Events

| Event                    | When it fires                            |
| ------------------------ | ---------------------------------------- |
| `incident_event.created` | A timeline event is added to an incident |
| `incident_event.updated` | A timeline event is updated              |
| `incident_event.deleted` | A timeline event is deleted              |

### Alerts and Pulses

| Event           | When it fires                 |
| --------------- | ----------------------------- |
| `alert.created` | An alert is created in Rootly |
| `pulse.created` | A pulse is created            |

### Workflows

| Event                           | When it fires                         |
| ------------------------------- | ------------------------------------- |
| `genius_workflow_run.queued`    | A workflow run is queued              |
| `genius_workflow_run.started`   | A workflow run begins executing       |
| `genius_workflow_run.completed` | A workflow run completes successfully |
| `genius_workflow_run.failed`    | A workflow run fails                  |
| `genius_workflow_run.canceled`  | A workflow run is cancelled           |

For payload examples for the most common event types, see [Event Payloads](/configuration/event-payloads).

***

## Delivery and Retries

Each event triggers a separate HTTP POST to every matching enabled endpoint. Rootly expects a `2xx` response within **10 seconds**. If the delivery fails or times out, Rootly retries automatically on the following schedule:

| Attempt   | Delay after previous failure |
| --------- | ---------------------------- |
| 1st retry | 15 seconds                   |
| 2nd retry | 1 minute                     |
| 3rd retry | 5 minutes                    |

After three failed retries the delivery is abandoned. You can inspect delivery history — including response status codes and response bodies — from the endpoint detail page in **Settings → Webhooks**.

<Note>
  Retries use the same payload as the original attempt. If your endpoint processed the event before returning an error, implement idempotency using the event ID in the payload to avoid duplicate processing.
</Note>

***

## Verifying Webhook Signatures

Every delivery includes an `X-Rootly-Signature` header containing a timestamp and an HMAC-SHA256 signature. Use this to confirm the payload originated from Rootly.

```txt theme={null}
X-Rootly-Signature: t=1492774588,v1=6657a869e8ecebeda32affa62cdca3fa51cad7e77a0e56ff536d0ce8e108d8bd
```

To verify:

1. Extract the timestamp (`t=`) and signature (`v1=`) from the header.
2. Concatenate the timestamp with the raw request body.
3. Compute an HMAC-SHA256 digest of that string using your endpoint's signing secret.
4. Compare the result to the `v1` value. If they match, the payload is authentic.

<CodeGroup>
  ```ruby Ruby theme={null}
  require 'openssl'
  require 'rack'

  header = request.headers['X-Rootly-Signature']
  parts = header.split(',')
  timestamp = parts[0].split('t=').last
  signature = parts[1].split('v1=').last
  secret = 'your_webhook_secret'

  expected = OpenSSL::HMAC.hexdigest('SHA256', secret, timestamp + request.body.read)
  is_valid = Rack::Utils.secure_compare(expected, signature)
  ```

  ```python Python theme={null}
  import hmac
  import hashlib

  header = request.headers['X-Rootly-Signature']
  parts = header.split(',')
  timestamp = parts[0].split('t=')[1]
  signature = parts[1].split('v1=')[1]
  secret = "your_webhook_secret"

  expected = hmac.new(
      key=secret.encode(),
      msg=(timestamp + request.data.decode()).encode(),
      digestmod=hashlib.sha256
  ).hexdigest()

  is_valid = hmac.compare_digest(expected, signature)
  ```

  ```javascript Node.js theme={null}
  const crypto = require('crypto');

  const header = request.headers['x-rootly-signature'];
  const parts = header.split(',');
  const timestamp = parts[0].split('t=')[1];
  const signature = parts[1].split('v1=')[1];
  const secret = "your_webhook_secret";

  // Use the raw request body, not the parsed body.
  // In Express, configure express.raw({ type: 'application/json' }) and use req.body directly,
  // or capture the raw body via a middleware before any JSON parsing.
  const rawBody = request.rawBody; // string or Buffer

  const expected = Buffer.from(
    crypto.createHmac('sha256', secret)
      .update(timestamp + rawBody)
      .digest('hex')
  );
  const received = Buffer.from(signature);
  const isValid = expected.length === received.length &&
    crypto.timingSafeEqual(expected, received);
  ```
</CodeGroup>

<Tip>
  To prevent replay attacks, also check that the timestamp in the header is within a few minutes of the current time before accepting the payload.
</Tip>

***

## Manage Endpoints

From **Settings → Webhooks** you can:

* **Enable or disable** an endpoint without deleting it — disabled endpoints receive no deliveries.
* **Edit** the name, URL, or event subscriptions at any time.
* **View delivery history** for each endpoint, including the HTTP status code and response body for each attempt.
* **Delete** an endpoint to permanently stop deliveries to that URL.

***

## Troubleshooting

<AccordionGroup>
  <Accordion title="My endpoint is not receiving events" icon="circle-question">
    Check that the endpoint is **enabled** in **Settings → Webhooks**. Also verify that the event types you expect are included in the endpoint's subscription, or that the subscription list is empty (which receives all events). If the URL requires authentication headers or is behind an IP allowlist, those must be handled at the receiving server — Rootly sends only the `X-Rootly-Signature` header alongside the standard `Content-Type: application/json` header.
  </Accordion>

  <Accordion title="Deliveries are failing with a timeout" icon="circle-exclamation">
    Rootly waits up to 10 seconds for a response. If your endpoint takes longer to process, return a `200` immediately and handle the payload asynchronously. Rootly will retry failed deliveries up to three times before abandoning the delivery.
  </Accordion>

  <Accordion title="I lost my signing secret" icon="key">
    You can regenerate the signing secret from the endpoint's edit form in **Settings → Webhooks**. Regenerating immediately invalidates the old secret, so update your server-side verification logic before regenerating.
  </Accordion>

  <Accordion title="How do I test my endpoint locally?" icon="laptop-code">
    Use a tool like [ngrok](https://ngrok.com) or [localtunnel](https://theboroer.github.io/localtunnel-www/) to expose a local port over a public HTTPS URL, then register that URL as a webhook endpoint in Rootly. Trigger a test event by creating or updating an incident, and inspect the delivery in the endpoint detail view.
  </Accordion>
</AccordionGroup>
