Static Documentation

Sentry Integration API

Version latest · Updated 2026-06-17
Interactive docs View on GitHub

Sentry integration API#

This document describes the IncidentRelay backend API behavior for the Sentry incoming integration.

Endpoint#

POST /api/integrations/sentry/{route_id}

This endpoint receives Sentry Internal Integration webhooks.

Unlike Alertmanager, Zabbix and Generic Webhook integrations, the Sentry endpoint does not use an IncidentRelay intake token. Authentication is based on the route id and Sentry-Hook-Signature verification.

Required headers#

HeaderRequiredDescription
Content-Type: application/jsonYesSentry sends JSON webhook payloads.
Sentry-Hook-SignatureYesHMAC signature generated by Sentry using the Internal Integration Client Secret.
Sentry-Hook-ResourceRecommendedSentry resource name, for example event_alert, metric_alert, issue.

Route requirements#

The route identified by {route_id} must:

  • exist;
  • have source=sentry;
  • be enabled;
  • belong to an active team and active group;
  • have integration_config.sentry.webhook_secret configured.

Example stored route config:

{
  "sentry": {
    "webhook_secret": "client-secret-from-sentry"
  }
}

API serialization must not return the secret. It should return only:

{
  "sentry": {
    "has_webhook_secret": true,
    "webhook_path": "/api/integrations/sentry/42"
  }
}

Signature verification#

IncidentRelay validates the request by calculating an HMAC-SHA256 digest over the raw request body using the route's Sentry webhook secret.

Pseudo-code:

expected = hmac.new(
    secret.encode("utf-8"),
    raw_body,
    hashlib.sha256,
).hexdigest()

valid = hmac.compare_digest(expected, request.headers["Sentry-Hook-Signature"])

The raw request body must be used exactly as received.

Response codes#

StatusErrorMeaning
404route_not_foundNo route exists for {route_id}.
400route_source_mismatchRoute exists but is not source=sentry.
403route_disabledRoute is disabled or deleted.
403route_team_inactiveRoute team is deleted or inactive.
403route_group_inactiveRoute group is deleted or inactive.
409sentry_secret_not_configuredRoute has no Sentry Client Secret.
403sentry_signature_missingRequest does not include Sentry-Hook-Signature.
403sentry_signature_invalidSignature does not match the body and secret.
400validation_errorRequest body is not a valid Sentry JSON payload.

Normalized alert output#

The normalizer returns a list with one IncidentRelay alert object.

Example event_alert.triggered normalized output:

{
  "source": "sentry",
  "team_slug": null,
  "external_id": "12345",
  "dedup_key": "sentry:issue:12345",
  "title": "ZeroDivisionError",
  "message": "division by zero",
  "severity": "critical",
  "status": "firing",
  "labels": {
    "alertname": "SentryIssueAlert",
    "severity": "critical",
    "sentry_resource": "event_alert",
    "sentry_action": "triggered",
    "organization_slug": "acme",
    "organization_name": "Acme",
    "project_slug": "backend-api",
    "project_name": "Backend API",
    "issue_id": "12345",
    "issue_short_id": "BACKEND-1",
    "event_id": "event-abc",
    "environment": "production",
    "level": "error",
    "sentry_url": "https://sentry.example.com/issues/12345/"
  },
  "payload": {}
}

Severity mapping#

Sentry level/statusIncidentRelay severity
fatalcritical
criticalcritical
errorcritical
warningwarning
warnwarning
infoinfo
debuginfo
resolvedinfo
okinfo
unknownwarning

Status mapping#

Sentry resource/actionIncidentRelay status
event_alert.triggeredfiring
metric_alert.criticalfiring
metric_alert.warningfiring
metric_alert.resolvedresolved
issue.createdfiring
issue.unresolvedfiring
issue.resolvedresolved
issue.ignoredresolved
issue.archivedresolved

Deduplication keys#

Issue-based alerts:

sentry:issue:<issue_id>

Metric alerts:

sentry:metric:<sentry_alert_id>

Fallback:

make_dedup_key("sentry", external_id, title, labels)

Route create/update payload#

Creating a route without a secret is allowed so the user can get the webhook URL first:

{
  "team_id": 1,
  "name": "Sentry Backend",
  "source": "sentry",
  "rotation_id": null,
  "escalation_policy_id": null,
  "channel_ids": [],
  "matchers": {},
  "group_by": ["project_slug", "issue_id"],
  "integration_config": {},
  "enabled": true
}

Saving the secret later:

{
  "team_id": 1,
  "name": "Sentry Backend",
  "source": "sentry",
  "rotation_id": null,
  "escalation_policy_id": null,
  "channel_ids": [],
  "matchers": {},
  "group_by": ["project_slug", "issue_id"],
  "integration_config": {
    "sentry": {
      "webhook_secret": "client-secret-from-sentry"
    }
  },
  "enabled": true
}

On update, an empty Sentry secret means: keep the existing saved secret.

Switching a route from source=sentry to another source should clear integration_config.