---
title: "The generic webhook source"
description: "Send alerts to Firefight from Datadog, Grafana, Prometheus Alertmanager, or any tool that can POST JSON."
canonical: https://firefight.app/docs/alerts/generic-webhook/
---

# The generic webhook source

The generic webhook source accepts a POST request with a JSON body from any monitoring tool. You tell Firefight where each field lives in the payload with a configurable mapping, so one mechanism covers Datadog, Grafana, Prometheus Alertmanager, and anything else that can send JSON to a URL.

## Create a source

Go to **Settings → Alert Sources** and click **Add source**. Give it a name, pick the **Generic webhook** provider, and create it. Firefight generates a unique ingest URL and a secret token for this source. Copy both from the sources table. The URL has this shape:

```
https://<your Firefight host>/api/v1/alerts/<endpoint-path>
```

The endpoint path is a random identifier unique to this source. Use the exact URL shown in **Settings → Alert Sources**.

## Authentication

Every request must carry the source's secret token in one of two headers. Firefight accepts either, so use whichever your tool can set.

| Header | Format |
|---|---|
| `Authorization` | `Bearer <token>` |
| `X-Firefight-Token` | `<token>` |

Requests with a missing or wrong token are rejected with `401`. Requests to an unknown or disabled source URL get `404`.

## What a request looks like

With no custom mapping, Firefight reads these top-level keys from the payload:

```json
{
  "id": "evt-8271",
  "title": "High error rate on checkout",
  "description": "5xx rate above 5% for 10 minutes",
  "service": "checkout",
  "severity": "critical",
  "status": "firing",
  "environment": "production"
}
```

Sent with curl:

```bash
curl -X POST "https://<your Firefight host>/api/v1/alerts/<endpoint-path>" \
  -H "Authorization: Bearer <token>" \
  -H "Content-Type: application/json" \
  -d '{
    "id": "evt-8271",
    "title": "High error rate on checkout",
    "service": "checkout",
    "severity": "critical",
    "status": "firing"
  }'
```

A successful request returns a JSON body like `{"ok": true, "received": 1, "failed": 0}`. A payload where no mapped field resolves is rejected with `422`, which tells you the mapping does not fit the payload. Bodies over 512 KB are rejected, and each source is rate limited (60 alerts per minute by default), returning `429` when exceeded so well-behaved tools retry later.

## Field mapping

Edit the source in **Settings → Alert Sources** to change where each field is read from. A mapping row pairs a Firefight field with a dot-path into the JSON payload. Path segments descend into nested objects, and numeric segments index into arrays, so `alerts.0.labels.severity` means the `severity` key inside `labels` of the first element of the `alerts` array.

| Firefight field | Default path | Used for |
|---|---|---|
| `title` | `title` | The alert's display title and the incident name |
| `description` | `description` | The incident summary |
| `service` | `service` | Routing conditions, grouping, and catalog lookups |
| `severity_raw` | `severity` | Mapped to your incident severities via the source's severity map |
| `status` | `status` | Firing or resolved. Values like `resolved`, `ok`, `recovered`, and `closed` mark the alert resolved, anything else means firing |
| `external_id` | `id` | Idempotency. The same id is never ingested twice |
| `fingerprint` | `fingerprint` | Identity for deduplication. Generated from other fields when absent |
| `team` | `team` | Routing conditions and catalog lookups |
| `environment` | `environment` | Routing conditions and catalog lookups |

:::tip
Once the source has received at least one request, the edit dialog shows clickable keys extracted from the last received payload. Click a key to fill a mapping row instead of typing paths blind. Sending one real test alert first makes mapping much faster.
:::

The same dialog also maps the provider's severity strings, such as `critical` or `P1`, to your workspace's incident severities. Unmapped values fall back to the workspace default severity.

## Batched alerts with a batch path

Some tools deliver several alerts in one POST as an array. Set the **Batch path** on the source to the dot-path of that array, and Firefight fans the request out so each element becomes its own alert. Field paths are then resolved relative to each element, not the whole body. A batch can carry up to 100 alerts per request.

## Example: Prometheus Alertmanager

Alertmanager's webhook receiver posts a body with an `alerts` array:

```json
{
  "status": "firing",
  "alerts": [
    {
      "status": "firing",
      "labels": { "alertname": "HighErrorRate", "service": "checkout", "severity": "critical" },
      "annotations": { "summary": "High error rate", "description": "5xx above 5%" },
      "fingerprint": "a1b2c3d4e5f6"
    }
  ]
}
```

Set the batch path to `alerts` and map:

| Firefight field | Path |
|---|---|
| `title` | `labels.alertname` |
| `description` | `annotations.description` |
| `service` | `labels.service` |
| `severity_raw` | `labels.severity` |
| `status` | `status` |
| `fingerprint` | `fingerprint` |

Alertmanager sends `status: "resolved"` for the same fingerprint when the alert clears, which resolves the open alert in Firefight automatically.

## Example: Grafana

Grafana's webhook contact point (unified alerting) uses an Alertmanager-compatible shape with an `alerts` array, where each element carries `labels`, `annotations`, `status`, and `fingerprint`. Use the batch path `alerts` and the same mapping as Alertmanager above. If your alert rules put a summary in annotations, map `title` to `annotations.summary` instead of `labels.alertname`.

## Example: Datadog

Datadog webhooks let you define the JSON payload yourself with template variables, so the simplest setup is to emit Firefight's default field names directly. In Datadog, create a webhook integration with a custom payload:

```json
{
  "id": "$ID",
  "title": "$EVENT_TITLE",
  "description": "$EVENT_MSG",
  "status": "$ALERT_TRANSITION",
  "severity": "$ALERT_PRIORITY",
  "service": "checkout"
}
```

Set the `Authorization` header to `Bearer <token>` in the webhook's custom headers. No field mapping is needed because the payload already uses the default paths. Datadog's `$ALERT_TRANSITION` renders values like `Triggered` and `Recovered`, and `Recovered` marks the alert resolved in Firefight. Map Datadog priorities such as `P1` to your severities in the source's severity map.

:::note
Firefight has no dedicated Datadog integration. The generic webhook with a custom payload is the supported path.
:::

Once alerts arrive, they follow the normal pipeline. See [Routing rules](https://firefight.app/docs/alerts/routing-rules.md) for what happens next, and [Testing your routing](https://firefight.app/docs/alerts/testing-routing.md) to dry-run before going live.
