Sentry — Ingest Setup

This guide shows how to send Sentry alerts to Culprit in real time using a Sentry Internal Integration webhook.


Endpoint

POST https://ingest.theculprit.ai/ingest/<tenant_id>/<service_name>/sentry?token=<ingest_token>

Replace <tenant_id> and <service_name> with the values shown on your service's settings page.

Why the token is in the URL: Sentry webhooks cannot send a custom Authorization header, so the ingest token travels as a ?token= query parameter. Keep this URL private — it is equivalent to a secret.


Setup in Sentry

  1. In your Sentry organisation, open Settings → Developer Settings → Internal Integrations.
  2. Click Create New Internal Integration.
  3. Give the integration a name (e.g. Culprit).
  4. Expand Webhooks and toggle on the event types you want forwarded — typically Issue.
  5. Paste your full Culprit endpoint URL (including ?token=…) into the Webhook URL field.
  6. Save. Sentry will immediately send a test ping; Culprit ignores installation pings silently.

Assign the integration to the projects whose alerts should flow to this Culprit service.


Getting your ingest token

  1. Open the Culprit dashboard → Services → select the service.
  2. In the Ingest token section, click Issue.
  3. Copy the token shown — it is displayed once. After you navigate away, only the token prefix is visible.

To rotate a compromised token, click Rotate. The previous token remains valid for the grace period shown, giving you time to update the Sentry webhook URL without an interruption.


Optional: signature verification

By default Culprit authenticates requests using the ingest token in the URL. For additional protection you can also verify Sentry's per-request HMAC signature, so a leaked URL alone is not enough to forge events.

To enable signature verification:

  1. In Sentry, open your Internal Integration and copy the Client Secret.
  2. In the Culprit dashboard, go to the service's settings and open Verify webhook signatures.
  3. Paste the Client Secret and save.

Culprit will then validate the sentry-hook-signature header on every incoming request. Requests that fail the signature check are rejected with 401 invalid_producer_signature and are never stored.


How Culprit maps Sentry webhooks

Culprit recognises two payload shapes.

Issue alerts (data.event present)

Fired when a Sentry alert rule triggers on an error event.

| Sentry field | Culprit event field | Notes | |---|---|---| | data.triggered_rule | event_type | Falls back to data.event.title, then sentry.issue | | data.event.level | severity | fatalfatal; errorerror; warningwarning; infoinfo; debugdebug (pipeline normalises to CRITICAL / HIGH / MEDIUM / LOW downstream) | | data.event.title | message | Falls back to metadata.type: metadata.value, then the first exception value | | data.event, data.triggered_rule | context | issue_id, web_url, platform, release, tags, and the full original body are preserved |

Metric alerts (data.metric_alert present)

Fired when a Sentry metric alert changes state.

| Sentry field | Culprit event field | Notes | |---|---|---| | data.metric_alert.alert_rule.name | event_type | Falls back to sentry.metric_alert | | action | severity | criticalcritical; warningwarning; anything else → low | | data.description_title | message | Falls back to data.description_text, then Sentry metric alert | | data.metric_alert, data.web_url | context | query, time_window, status, web_url, and the full original body are preserved |

Installation pings and unknown shapes are silently ignored with a 400 unrecognized_payload — they are never vaulted.

PII in event titles, messages, tags, or hostnames (email addresses, IP addresses, API keys) is detected and tokenized before any storage or notification. The original values are preserved encrypted and are visible only in the incident detail view after authentication.


Worked example — issue alert

Sentry sends:

{
  "action": "triggered",
  "data": {
    "event": {
      "title": "ReferenceError: heck is not defined",
      "level": "error",
      "platform": "javascript",
      "issue_id": "1117540176",
      "metadata": {
        "type": "ReferenceError",
        "value": "heck is not defined"
      }
    },
    "triggered_rule": "Very Important Alert Rule!"
  }
}

Culprit event (before tokenization):

{
  "event_type": "Very Important Alert Rule!",
  "severity": "error",
  "source": "sentry",
  "message": "ReferenceError: heck is not defined",
  "context": {
    "issue_id": "1117540176",
    "platform": "javascript",
    "triggered_rule": "Very Important Alert Rule!",
    "raw": { ... }
  }
}

This event flows through the standard pipeline: encrypted vault storage → PII tokenization → storm check → embedding → correlation → root-cause analysis.


Successful response

{ "accepted": true, "ids": ["<vault_id>"] }

Error responses

| Status | Body | Meaning | |---|---|---| | 401 | { "error": "missing_auth" } | No ?token= parameter in the URL | | 401 | { "error": "invalid_ingest_token" } | Token is incorrect or has been rotated/revoked | | 401 | { "error": "invalid_producer_signature" } | Signature verification is enabled and the sentry-hook-signature header does not match | | 400 | { "error": "unrecognized_payload" } | Body is not valid JSON or does not match either Sentry shape | | 403 | { "error": "service_disabled" } | The service exists but monitoring is paused | | 404 | { "error": "service_deleted_or_not_found" } | The service does not exist | | 429 | { "error": "rate_limited" } | Storm protection active (sustained high volume) |