Skip to content

Latest commit

 

History

History
218 lines (173 loc) · 5.34 KB

File metadata and controls

218 lines (173 loc) · 5.34 KB
title Sentry Integration API
description Signed Sentry webhook endpoint, payload normalization and response codes.

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

Header Required Description
Content-Type: application/json Yes Sentry sends JSON webhook payloads.
Sentry-Hook-Signature Yes HMAC signature generated by Sentry using the Internal Integration Client Secret.
Sentry-Hook-Resource Recommended Sentry 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

Status Error Meaning
404 route_not_found No route exists for {route_id}.
400 route_source_mismatch Route exists but is not source=sentry.
403 route_disabled Route is disabled or deleted.
403 route_team_inactive Route team is deleted or inactive.
403 route_group_inactive Route group is deleted or inactive.
409 sentry_secret_not_configured Route has no Sentry Client Secret.
403 sentry_signature_missing Request does not include Sentry-Hook-Signature.
403 sentry_signature_invalid Signature does not match the body and secret.
400 validation_error Request 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/status IncidentRelay severity
fatal critical
critical critical
error critical
warning warning
warn warning
info info
debug info
resolved info
ok info
unknown warning

Status mapping

Sentry resource/action IncidentRelay status
event_alert.triggered firing
metric_alert.critical firing
metric_alert.warning firing
metric_alert.resolved resolved
issue.created firing
issue.unresolved firing
issue.resolved resolved
issue.ignored resolved
issue.archived resolved

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.