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

# Webhooks

> Receive real-time HTTP notifications from Sentry when issues are created, resolved, or updated.

Sentry can send HTTP POST requests to any URL you control whenever something happens—a new issue is created, an error fires an alert, an issue is resolved, and more. There are two ways to set this up: **Service Hooks** for simple project-level notifications, and **Sentry App webhooks** for richer, event-driven integrations built on the Integration Platform.

## Service Hooks

Service Hooks are the simplest way to receive Sentry notifications over HTTP. You configure a URL and a set of events, and Sentry sends a signed POST request to that URL whenever those events occur in a project.

### Available events

| Event           | When it fires                         |
| --------------- | ------------------------------------- |
| `event.alert`   | An alert rule fires for an event      |
| `event.created` | A new event is received and processed |

### Create a Service Hook

<Steps>
  <Step title="Enable Service Hooks for the project">
    Service Hooks require the `servicehooks` feature to be enabled for your project. Contact your Sentry administrator if you do not see the option below.
  </Step>

  <Step title="Open project settings">
    Go to **Settings > \[Project] > Service Hooks**.
  </Step>

  <Step title="Add a hook">
    Click **Add a new hook**, enter your endpoint URL, and select the events you want to receive.
  </Step>

  <Step title="Save">
    Click **Save**. Sentry immediately starts sending events to your URL.
  </Step>
</Steps>

### Create a Service Hook via API

You can also create Service Hooks programmatically using the Sentry API.

```bash theme={null}
curl https://sentry.io/api/0/projects/{organization_id_or_slug}/{project_id_or_slug}/hooks/ \
  -H "Authorization: Bearer <your_auth_token>" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/sentry-hook",
    "events": ["event.alert", "event.created"]
  }'
```

A successful response returns the hook details including the `secret` you use to verify incoming requests:

```json theme={null}
{
  "id": "4f9d73e63b7144ecb8944c41620a090b",
  "dateCreated": "2018-11-06T21:20:08.143Z",
  "url": "https://example.com/sentry-hook",
  "events": ["event.alert", "event.created"],
  "secret": "8fcac28aaa4c4f5fa572b61d40a8e084364db25fd37449c299e5a41c0504cbc2",
  "status": "active"
}
```

<Warning>
  Store the `secret` securely. You need it to verify that incoming webhook requests actually come from Sentry. It is not shown again after creation.
</Warning>

## Webhook payload

Sentry sends a JSON payload via HTTP POST to your endpoint. The exact structure depends on the event type, but every payload includes the event data in the request body.

### Example `event.alert` payload

```json theme={null}
{
  "id": "abc123",
  "project": "my-project",
  "project_name": "My Project",
  "culprit": "raven.scripts.runner in main",
  "url": "https://sentry.io/organizations/my-org/issues/123/",
  "message": "This is an example Python exception",
  "triggering_rules": ["My Alert Rule"],
  "event": {
    "event_id": "9fac2ceed9344f2bbfdd1fdacb0ed9b1",
    "level": "error",
    "tags": [
      ["environment", "production"],
      ["release", "v1.2.3"]
    ],
    "platform": "python",
    "datetime": "2024-01-15T12:00:00.000000Z"
  }
}
```

## Verify webhook signatures

Sentry signs every webhook request using HMAC-SHA256 with the hook's secret. You should verify this signature before processing the payload to confirm the request came from Sentry.

The signature is in the `X-Sentry-Hook-Signature` request header.

<Tabs>
  <Tab title="Python">
    ```python theme={null}
    import hashlib
    import hmac
    from flask import Flask, request, abort

    app = Flask(__name__)
    WEBHOOK_SECRET = "your_webhook_secret_here"

    @app.route("/sentry-hook", methods=["POST"])
    def sentry_hook():
        signature = request.headers.get("X-Sentry-Hook-Signature", "")
        body = request.get_data()

        expected = hmac.new(
            key=WEBHOOK_SECRET.encode("utf-8"),
            msg=body,
            digestmod=hashlib.sha256,
        ).hexdigest()

        if not hmac.compare_digest(expected, signature):
            abort(401)

        payload = request.get_json()
        # process payload...
        return "", 200
    ```
  </Tab>

  <Tab title="Node.js">
    ```javascript theme={null}
    const crypto = require("crypto");
    const express = require("express");

    const app = express();
    const WEBHOOK_SECRET = "your_webhook_secret_here";

    app.post(
      "/sentry-hook",
      express.raw({ type: "application/json" }),
      (req, res) => {
        const signature = req.headers["x-sentry-hook-signature"] || "";
        const expected = crypto
          .createHmac("sha256", WEBHOOK_SECRET)
          .update(req.body)
          .digest("hex");

        if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature))) {
          return res.status(401).send("Invalid signature");
        }

        const payload = JSON.parse(req.body);
        // process payload...
        res.status(200).send("OK");
      }
    );
    ```
  </Tab>
</Tabs>

<Warning>
  Always use a constant-time comparison function (such as `hmac.compare_digest` in Python or `crypto.timingSafeEqual` in Node.js) when comparing signatures. Regular string comparison is vulnerable to timing attacks.
</Warning>

## Sentry App webhooks

If you need richer webhook events—beyond `event.alert` and `event.created`—build a Sentry App using the Integration Platform. Sentry App webhooks support a much broader set of events:

| Resource       | Events                                                                                        |
| -------------- | --------------------------------------------------------------------------------------------- |
| `issue`        | `issue.created`, `issue.resolved`, `issue.ignored`, `issue.assigned`, `issue.unresolved`      |
| `error`        | `error.created`                                                                               |
| `comment`      | `comment.created`, `comment.updated`, `comment.deleted`                                       |
| `installation` | `installation.created`, `installation.deleted`                                                |
| `metric_alert` | `metric_alert.critical`, `metric_alert.warning`, `metric_alert.resolved`, `metric_alert.open` |
| `event_alert`  | `event_alert.triggered`                                                                       |

### Create a Sentry App

1. Go to **Settings > Developer Settings > New Internal Integration** (for private use within your organization) or **New Public Integration** (to publish on the Sentry marketplace).
2. Enter a name and webhook URL.
3. Under **Webhook**, enable the resource categories you want to receive.
4. Save the app and install it on your organization.

Sentry sends signed POST requests to your webhook URL for each subscribed event. The signature header is `Sentry-Hook-Signature` and uses the same HMAC-SHA256 scheme as Service Hooks.

### Example `issue.created` payload

```json theme={null}
{
  "action": "created",
  "installation": {
    "uuid": "a8e5d37a-696c-4c54-aac1-4a51b40ce7d9"
  },
  "data": {
    "issue": {
      "id": "1234567890",
      "shortId": "MY-PROJECT-1A2B",
      "title": "TypeError: Cannot read properties of undefined",
      "culprit": "app/utils/api in fetchUser",
      "level": "error",
      "status": "unresolved",
      "firstSeen": "2024-01-15T12:00:00.000000Z",
      "lastSeen": "2024-01-15T12:00:00.000000Z",
      "count": 1,
      "project": {
        "id": "1",
        "name": "my-project",
        "slug": "my-project"
      }
    }
  },
  "actor": {
    "type": "application",
    "id": "sentry",
    "name": "Sentry"
  }
}
```

## Respond to webhooks quickly

Sentry expects your endpoint to respond with a `2xx` status code within a few seconds. If your endpoint takes longer to process the payload, acknowledge the request immediately and handle the work asynchronously.

```python theme={null}
from threading import Thread
from flask import Flask, request

app = Flask(__name__)

def process_payload(payload):
    # slow processing here
    pass

@app.route("/sentry-hook", methods=["POST"])
def sentry_hook():
    payload = request.get_json()
    Thread(target=process_payload, args=(payload,)).start()
    return "", 200  # respond immediately
```

## Retry behavior

Sentry retries failed webhook deliveries if your endpoint returns a non-`2xx` response or times out. Make your endpoint idempotent so that processing the same event twice does not cause unintended side effects. You can use the event `id` field to deduplicate.
