# Cloudflare Webhooks

Cloudflare Notifications (Alerts) can deliver alerts to a **generic webhook**
destination. Cloudflare sends an HTTP POST with a JSON body to your endpoint
whenever a notification policy fires, for example a DDoS attack, a failing
origin health check or an expiring certificate.

This skill covers Cloudflare Notifications webhooks only. It does not cover
Cloudflare Workers, which are code you write and host.

## When to Use This Skill

* How do I receive Cloudflare Notifications / alert webhooks?
* How do I verify / authenticate a Cloudflare webhook?
* Why is my Cloudflare `cf-webhook-auth` check failing?
* How do I handle `advanced_ddos_attack_l7_alert` or `health_check_status_notification`?
* What does a Cloudflare webhook payload look like (`name`, `text`, `data`, `alert_type`)?
* Why does the Cloudflare "Save and Test" message have no `alert_type`?

## No Signature: Shared Secret Header Only

Cloudflare **does not sign generic webhook payloads**. There is no HMAC, no
signature algorithm and no timestamp. When you create the webhook in the
dashboard you can enter a **Secret**; Cloudflare then sends it **verbatim** in
the `cf-webhook-auth` header of every request.

> If this header is not present, or is not your specified value, you should
> reject the webhook. — [Cloudflare docs](https://developers.cloudflare.com/notifications/get-started/configure-webhooks/#generic-webhooks)

Cloudflare recommends always setting a secret for generic webhooks. Without
one, nothing authenticates the request.

## Verification (core)

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

function verifyCloudflareAuth(header, secret) {
  if (!header || !secret) return false;                  // fail closed
  const a = Buffer.from(String(header), 'utf8');
  const b = Buffer.from(String(secret), 'utf8');
  return a.length === b.length && crypto.timingSafeEqual(a, b); // length guard first
}

// header name is case-insensitive in HTTP; Node lowercases it
verifyCloudflareAuth(req.headers['cf-webhook-auth'], process.env.CLOUDFLARE_WEBHOOK_SECRET);

```

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

Cloudflare has no SDK with a webhook verification helper, so every example
does this compare directly (`crypto.timingSafeEqual` in Node,
`hmac.compare_digest` in Python). Authenticate before you parse the JSON body.

## Common Event Types

The event name is the payload's `alert_type` field. These are the values shown in
Cloudflare's [webhook payload schema](https://developers.cloudflare.com/notifications/reference/webhook-payload-schema/);
other notification types use their own `alert_type`, and the shape of `data`
varies by type.

| `alert_type` | Triggered when |
| --- | --- |
| `advanced_ddos_attack_l4_alert` | A Layer 3/4 DDoS attack is detected |
| `advanced_ddos_attack_l7_alert` | A Layer 7 DDoS attack is detected |
| `dedicated_ssl_certificate_event_type` | A dedicated or custom SSL certificate event, such as expiration |
| `health_check_status_notification` | An origin health check changes status |
| `workers_alert` | A Workers usage alert fires |
| `workers_observability_alert` | A Workers observability alert fires |
| `access_custom_certificate_expiration_type` | An Access custom certificate is nearing expiration |
| *(no `alert_type`)* | The "Save and Test" message sent from the dashboard: only a `text` field |

Payload fields: `name`, `text`, `data`, `ts` (unix seconds), `account_id`,
`policy_id`, `policy_name`, `alert_type`, `alert_correlation_id` and
`alert_event` (`ALERT_STATE_EVENT_START` / `ALERT_STATE_EVENT_END`). Cloudflare
says `account_id`, `policy_id` and `alert_type` may be absent for some alerts.
See [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/cloudflare-webhooks/references/overview.md).

## Environment Variables

```bash
# The value you entered in the Secret field of the Cloudflare webhook
CLOUDFLARE_WEBHOOK_SECRET=your_webhook_secret

```

With no secret configured, the examples reject every request with a 500 rather
than accept unauthenticated webhooks.

## Local Development

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

```

No account required — the CLI creates a guest account on first run and gives
you a public HTTPS URL plus a web UI for inspecting requests (including the
`cf-webhook-auth` header). Use port `8000` for the FastAPI example.

Cloudflare delivers generic webhooks only to a publicly resolvable IP on port
80 or 443, so a `localhost` URL won't work without a tunnel like this.

## Reference Materials

* [references/overview.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/cloudflare-webhooks/references/overview.md) — Payload schema, `alert_type` values, `alert_event` states
* [references/setup.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/cloudflare-webhooks/references/setup.md) — Creating the webhook destination and attaching it to a notification policy
* [references/verification.md](https://github.com/hookdeck/webhook-skills/blob/main/skills/cloudflare-webhooks/references/verification.md) — The `cf-webhook-auth` header in detail, replay caveats, debugging failures