# Dynatrace Webhooks

Dynatrace (Classic) problem notifications can POST to a custom webhook URL when a problem opens
or is resolved. Requests are **not signed**. The body is a payload template you write
(JSON, text or XML) using placeholders such as `{ProblemID}`, `{State}`, `{ProblemTitle}`,
`{ImpactedEntity}` and `{ProblemImpact}`; Dynatrace adds no event field of its own. You authenticate
requests with a secret header you add as an **Additional HTTP header** on the integration.

> **Send the secret in a custom header, not as a token in the webhook URL.** Dynatrace allows a
> token in the URL, but Hookdeck can't check it and URLs leak into logs.

## When to Use This Skill

* How do I receive Dynatrace problem notification webhooks?
* How do I authenticate a Dynatrace webhook? (No signature: use a secret header)
* How do I handle opened and resolved problems from a Dynatrace payload template?
* Why does my Dynatrace webhook endpoint return 401?

## Verification (core)

```javascript
const crypto = require('crypto');

// Hash both sides: equal-length buffers, so timingSafeEqual never throws.
function safeEqual(a, b) {
  const ha = crypto.createHash('sha256').update(String(a)).digest();
  const hb = crypto.createHash('sha256').update(String(b)).digest();
  return crypto.timingSafeEqual(ha, hb);
}

function verifyDynatraceRequest(headers,
    name = process.env.DYNATRACE_WEBHOOK_HEADER || 'Authorization',
    secret = process.env.DYNATRACE_WEBHOOK_SECRET) {
  if (!secret) return false;                          // never accept when unconfigured
  const received = headers[name.toLowerCase()];       // case-insensitive lookup
  return Boolean(received) && safeEqual(received, secret); // WHOLE value, no "Bearer " parsing
}

```

* The header name is whatever you typed in Dynatrace (`DYNATRACE_WEBHOOK_HEADER`, default
  `Authorization`). The expected value (`DYNATRACE_WEBHOOK_SECRET`) is the exact, whole header
  value, including any `Bearer ` prefix you typed. Do not strip or parse the prefix.
* Return `401` for a missing header or mismatch, `200` once handled.
* Read the body raw (`express.raw({ type: '*/*' })`) so any content type works, and parse it only
  after the credential check.
* **A static shared credential authenticates the sender, not the body, and gives no replay
  protection** (no signature, no timestamp). Use HTTPS and rotate the credential if it leaks.

> **For complete handlers with tests**, see [examples/express/](https://github.com/hookdeck/webhook-skills/tree/main/skills/dynatrace-webhooks/examples/express/), [examples/nextjs/](https://github.com/hookdeck/webhook-skills/tree/main/skills/dynatrace-webhooks/examples/nextjs/), [examples/fastapi/](https://github.com/hookdeck/webhook-skills/tree/main/skills/dynatrace-webhooks/examples/fastapi/).

## Payload and Problem States

The payload is **your template**, so the shape below is an illustrative example, not a vendor
schema. These examples assume this JSON template and dispatch on `State`:

```json
{
  "ProblemID": "{ProblemID}",
  "State": "{State}",
  "ProblemTitle": "{ProblemTitle}",
  "ImpactedEntity": "{ImpactedEntity}"
}

```

| `State` | Meaning |
| --- | --- |
| `OPEN` | A problem was opened (the only value Dynatrace's docs show, in the test-notification example) |

A notification is also sent when a problem is resolved, but the docs don't show the `{State}` value
it carries. The examples handle `OPEN` and log any other value; capture a real resolution (or
check your Dynatrace environment) before matching on it. Include `{State}` in your template or you
can't tell the two apart. The docs list no `{ProblemURL}` placeholder (their example has a literal
`"Problem URL": "https://example.com"`).

## Environment Variables

```bash
DYNATRACE_WEBHOOK_HEADER=Authorization        # header name you added in Dynatrace
DYNATRACE_WEBHOOK_SECRET=your-secret-value    # exact, whole header value

```

## Local Development

Dynatrace can't reach `localhost`. Tunnel with the Hookdeck CLI (no account required):

```bash
npx hookdeck-cli listen 3000 dynatrace --path /webhooks/dynatrace

```

Use the printed URL as the webhook URL in Dynatrace.

## Resources

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dynatrace-webhooks/references/overview.md) - What Dynatrace problem webhooks are, payload template
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dynatrace-webhooks/references/setup.md) - Add the custom webhook integration and secret header
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/dynatrace-webhooks/references/verification.md) - Header check details, gotchas, debugging 401s
* [examples/](https://github.com/hookdeck/webhook-skills/tree/main/skills/dynatrace-webhooks/examples/) - Runnable Express, Next.js and FastAPI handlers with tests