Datadog — Ingest Setup

This guide shows how to route Datadog alerts straight to Culprit using Datadog's built-in Webhooks integration.


Endpoint

POST https://ingest.theculprit.ai/ingest/<tenant_id>/<service_name>/datadog

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


Authentication

Requests must carry an ingest token in the Authorization header:

Authorization: Bearer <ingest_token>

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 shown.

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


Setup in Datadog

Datadog webhook bodies are user-defined templates. You must paste the body template below so Culprit receives the fields it needs.

  1. Open Integrations → Webhooks → New Webhook.
  2. Set URL to your Culprit endpoint shown above.
  3. Under Custom Headers, add:
    • Header: Authorization
    • Value: Bearer <ingest_token>
  4. In the Payload field, replace the default content with this template:
{ "title": "$EVENT_TITLE", "body": "$EVENT_MSG", "alert_type": "$ALERT_TYPE", "priority": "$PRIORITY", "tags": "$TAGS", "id": "$ID", "date": "$DATE" }
  1. Save the webhook.
  2. In your Datadog monitors, add the webhook as a notification channel using @webhook-<webhook_name>.

How Culprit maps Datadog alerts

Field mapping

| Datadog field | Culprit event field | Notes | |---|---|---| | body | message | Falls back to title if body is absent or unsubstituted | | title | event_type suffix + message fallback | Used as message when body is absent | | priority | severity | P1critical (overrides alert_type) | | alert_type | severity | errorerror; warningwarning; success / infoinfo | | alert_type, priority, tags, id, date | context | All preserved for correlation and RCA |

Severity resolution (in priority order)

  1. P1 prioritycritical (overrides everything else)
  2. alert_type: errorerror
  3. alert_type: warningwarning
  4. alert_type: success or alert_type: infoinfo
  5. No usable fieldswarning

Unsubstituted variables

If a field contains an unsubstituted Datadog variable (for example $EVENT_MSG or $ALERT_TYPE) or is blank, Culprit treats it as absent. An event where both body and title are absent or unsubstituted is rejected with 400 unrecognized_payload.

PII in any field (IP addresses, hostnames, email 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

Datadog sends:

{
  "title": "[Triggered] 5xx spike on checkout",
  "body": "Error rate 12% on api-1.acme.internal",
  "alert_type": "error",
  "priority": "P1",
  "tags": "env:prod,service:checkout",
  "id": "1234567890",
  "date": "1716926400000"
}

Culprit event (before tokenization):

{
  "event_type": "datadog.error",
  "severity": "critical",
  "source": "datadog",
  "message": "Error rate 12% on api-1.acme.internal",
  "context": {
    "alert_type": "error",
    "priority": "P1",
    "tags": "env:prod,service:checkout",
    "id": "1234567890",
    "date": "1716926400000"
  }
}

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 Authorization header | | 401 | { "error": "invalid_ingest_token" } | Token is incorrect or has been rotated/revoked | | 400 | { "error": "unrecognized_payload" } | Body is not valid JSON, both body and title are absent or unsubstituted, or the payload does not match the expected 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) |