How to Receive Webhooks from Sentry (Self-Hosted, Step by Step)

Receive and verify Sentry Integration Platform webhooks on your own server: Sentry-Hook-Signature HMAC-SHA256 validation, the 1-second response budget, Docker setup, and RabbitMQ routing, every command verified locally.

By the end of this guide you will have a self-hosted endpoint that receives Sentry webhooks, verifies the HMAC-SHA256 signature on every delivery, and routes the payloads to a log, RabbitMQ, or any other destination. Every command below was run and verified against the real Core Webhook Module Docker image before publishing; you can copy-paste them in order. It works the same for sentry.io and self-hosted Sentry, because both use the Integration Platform webhook mechanism.

How Sentry webhooks work

Sentry's signed webhooks come from the Integration Platform: you create an integration (an internal one for your own organization), give it a webhook URL, and pick which events it receives. When an event fires, Sentry sends an HTTP POST with these headers:

  • Content-Type: application/json
  • Request-ID: a unique ID per request
  • Sentry-Hook-Resource: the resource that triggered it (installation, issue, error, comment, event_alert, metric_alert)
  • Sentry-Hook-Timestamp: when the request was sent
  • Sentry-Hook-Signature: HMAC-SHA256 of the JSON request body, keyed with your integration's Client Secret, as a plain hex digest

The payload always has the same shape: action (for example created), installation (the UUID mapping the request to your integration), data (the resource, for example the issue), and actor (who triggered it). Sentry's own verification example is exactly hex(HMAC-SHA256(client_secret, body)) compared against the header.

One number worth knowing before you design anything: Sentry expects a response within 1 second, otherwise the delivery counts as a timeout. A receiver that validates, acknowledges, and hands off asynchronously is not optional here; it is the spec.

Note: Sentry also has a legacy "WebHooks" alert plugin. It does not sign requests, so this guide uses the Integration Platform, which does.

Prerequisites

Step 1: Configure the receiver

Create a working directory with two files.

webhooks.json:

{
    "sentry_events": {
        "data_type": "json",
        "module": "log",
        "hmac": {
            "secret": "{$SENTRY_CLIENT_SECRET}",
            "header": "Sentry-Hook-Signature",
            "algorithm": "sha256"
        }
    }
}

connections.json:

{}

What each parameter does:

  • sentry_events is the webhook ID; it becomes the URL path (/webhook/sentry_events).
  • module: log prints each verified payload to the container log. Zero dependencies for the first run; we swap in RabbitMQ at the end.
  • hmac recomputes the HMAC-SHA256 of the raw body and compares it against the Sentry-Hook-Signature header in constant time. Sentry sends a plain hex digest with no prefix, which is accepted as-is; header lookup is case-insensitive.
  • {$SENTRY_CLIENT_SECRET} pulls the secret from an environment variable, so it never lives in a config file. The value is your integration's Client Secret from Step 4, not something you generate yourself.

Step 2: Run it

docker run -d --name webhook-gateway -p 8000:8000 \
  -v "$PWD/webhooks.json:/app/webhooks.json:ro" \
  -v "$PWD/connections.json:/app/connections.json:ro" \
  -e SENTRY_CLIENT_SECRET="your-integration-client-secret" \
  spiderhash/webhook:latest

Confirm it is up:

docker logs webhook-gateway
# ... INFO: Application startup complete.

The receiver now answers on http://localhost:8000/webhook/sentry_events, with auto-generated API docs at http://localhost:8000/docs.

Step 3: Test it locally

You do not need Sentry to test; you need a request signed the way Sentry signs. Save a realistic issue.created payload:

cat > issue_created.json <<'EOF'
{"action":"created","installation":{"uuid":"64bf2cf2-37ee-46ae-a093-b7a52bd41a2a"},"data":{"issue":{"id":"1170820242","shortId":"API-42","title":"TypeError: 'NoneType' object is not subscriptable","culprit":"api.views.process_payload","level":"error","status":"unresolved","platform":"python","project":{"id":"4","name":"api","slug":"api"},"firstSeen":"2026-08-29T10:15:00.000000Z","lastSeen":"2026-08-29T10:15:00.000000Z","count":"1"}},"actor":{"type":"application","id":"sentry","name":"Sentry"}}
EOF

Compute the signature over the exact bytes of the file and send it as plain hex, exactly like Sentry does:

SIG=$(openssl dgst -sha256 -hmac "your-integration-client-secret" -hex issue_created.json | awk '{print $NF}')

curl -s -w "\nHTTP %{http_code}\n" -X POST http://localhost:8000/webhook/sentry_events \
  -H "Content-Type: application/json" \
  -H "Sentry-Hook-Resource: issue" \
  -H "Sentry-Hook-Signature: $SIG" \
  --data-binary @issue_created.json

Expected result:

{"message":"200 OK"}
HTTP 200

Now prove the verification works. A tampered or wrong signature:

{"detail":"Invalid HMAC signature"}
HTTP 401

And a request with no signature header at all:

{"detail":"Missing Sentry-Hook-Signature header"}
HTTP 401

Those are the exact responses the gateway returns. docker logs webhook-gateway shows the accepted payload.

Step 4: Create the integration in Sentry

In Sentry (sentry.io or your self-hosted instance): Settings > Developer Settings, create a new Internal Integration:

  • Name: something like webhook-gateway
  • Webhook URL: https://your-domain/webhook/sentry_events
  • Permissions: give it read access to the resources you subscribe to (for example Issue & Event: Read)
  • Webhooks: check the events you want (issue, error, comment, ...)

Save, then copy the Client Secret shown on the integration page; that is the value for SENTRY_CLIENT_SECRET on the gateway. Restart the gateway container with the real secret.

For alert notifications, the integration also becomes available as an alert rule action ("Send a notification via webhook-gateway") once a webhook URL is set, which routes issue and metric alert webhooks (event_alert, metric_alert) to the same endpoint.

To trigger a real delivery, cause an event: resolve and unresolve an issue, add a comment, or fire a test error in a project the integration can see, then check docker logs webhook-gateway.

Troubleshooting

  • 401 {"detail":"Invalid HMAC signature"}: SENTRY_CLIENT_SECRET does not match the integration's Client Secret, or something between Sentry and the gateway re-encoded the body. The signature covers the raw bytes.
  • 401 {"detail":"Missing Sentry-Hook-Signature header"}: the request is not a signed Integration Platform delivery. The legacy WebHooks plugin sends unsigned requests; make sure the webhook URL is set on the internal integration, not the plugin.
  • Deliveries time out on Sentry's side: Sentry gives you 1 second to respond. The gateway acknowledges in milliseconds with the log module, but if you point a module at a slow downstream synchronously, you will start losing deliveries; hand off to a queue instead (next section).
  • installation webhooks arrive that you did not subscribe to: every integration receives installation.created and installation.deleted events regardless of the checkboxes; handle or ignore them by the Sentry-Hook-Resource header.

Going to production

Printing to logs is not a pipeline, and with Sentry's 1 second budget you want the fastest possible acknowledge-and-queue path. Here is the RabbitMQ variant, verified end to end (the signed test delivery above landed as a message in the sentry_events queue):

webhooks.json:

{
    "sentry_events": {
        "data_type": "json",
        "module": "rabbitmq",
        "connection": "rabbitmq_local",
        "module-config": {
            "queue_name": "sentry_events"
        },
        "hmac": {
            "secret": "{$SENTRY_CLIENT_SECRET}",
            "header": "Sentry-Hook-Signature",
            "algorithm": "sha256"
        }
    }
}

connections.json:

{
    "rabbitmq_local": {
        "type": "rabbitmq",
        "host": "{$RABBITMQ_HOST:localhost}",
        "port": "{$RABBITMQ_PORT:5672}",
        "user": "{$RABBITMQ_USER:guest}",
        "pass": "{$RABBITMQ_PASS:guest}"
    }
}

The same pattern works for 17 other destinations: archive error events to S3, publish to Kafka, or fan out to several at once with webhook chaining. Built-in rate limiting and retry handling cover the operational edges. The full authentication reference, including HMAC validation and 11 other methods, is in the docs.

Subscribe to Free Webhook Tool

Sign up now to get access to the library of members-only issues.
Jamie Larson
Subscribe