---
title: "Alert-Sources-API"
description: "Alert-Sources im Incident Management anlegen, konfigurieren und testen: Ingest-Tokens, optionale HMAC- oder Bearer-Authentifizierung, Payload-Mapping, Wartungsfenster und Statistiken je Source."
---

Eine **Alert-Source** ist eine eingehende Integration. Jede Webhook-Source hat ihre eigene Ingest-URL (`https://uptimeify.io/api/im/ingest/<token>`) und ein Payload-Mapping, das das JSON des Anbieters in einen Alert übersetzt. Wie du Zabbix, Datadog, Grafana und andere auf diese URL zeigen lässt, steht in den [Einrichtungsanleitungen](/de/api/incident-management/alert-sources). Diese Seite beschreibt die Endpunkte, mit denen du die Sources selbst verwaltest.

## Authentifizierung

Jeder Endpunkt braucht IM-Zugriff: eine IM-berechtigte Rolle (`admin`, `editor`, `responder`) oder einen organisationsweiten API-Token. Incident Management muss für die Organisation aktiviert sein. Ein kunden-gescopter Token wird mit `403 Forbidden` (`customerScopedTokenForbidden`) abgewiesen.

- **Lese-Endpunkte** (Liste, Detail, Presets, Payloads, Statistik, test-mapping) stehen jedem IM-berechtigten Aufrufer offen.
- **Schreib-Endpunkte** (Anlegen, Ändern, Löschen, Token- und Secret-Rotation, disable-auth, test-alert) verlangen zusätzlich die Rolle `admin` oder eine Team-Admin-Mitgliedschaft im Team der Source. Ein API-Token trägt die Rolle der Person, die ihn erstellt hat. Nimm also einen Token, den ein Organisations-Admin erstellt hat.

Sources sind auf deine Organisation beschränkt. Eine Source einer anderen Organisation antwortet mit `404 Not Found` (`imSourceNotFound`), nie mit `403`.

## Das Source-Objekt

Liste, Detail, Ändern und disable-auth liefern diese Form. Zugangsdaten tauchen darin nie auf: Ingest-Token, Bearer-Token und HMAC-Secret gibt es nur einmal, vom Endpunkt, der sie erzeugt.

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `id` | number | ID der Source. |
| `organizationId` | number | Besitzende Organisation. |
| `teamId` | number | Team, dessen Eskalation die Source speist. |
| `name` | string | Anzeigename, bis zu 120 Zeichen. |
| `type` | string | `zabbix`, `datadog`, `grafana`, `alertmanager`, `sentry`, `custom`, `api`, `email` oder `uptimeify_monitoring` (die eingebaute Brücke aus dem klassischen Monitoring, von der Plattform angelegt). |
| `authMode` | object | `{ url_token?, hmac?: { enabled }, bearer?: { enabled } }`. Siehe [Optionale Absender-Authentifizierung](#optionale-absender-authentifizierung). |
| `payloadMapping` | object | Feld-Mapping, das auf jede eingehende Payload angewendet wird. |
| `autoResolve` | boolean | Ob ein Resolve-Event den Incident automatisch schließt. Wird auf jeden neuen Incident übertragen. |
| `groupingWindowMinutes` | number oder null | Gruppierungsfenster. `null` gruppiert nur nach Dedup-Key. |
| `maintenance` | object oder null | Wiederkehrende Wartungsfenster, `{ windows: [...] }`. |
| `snoozeUntil` | ISO 8601 oder null | Stummgeschaltet bis zu diesem Zeitpunkt. |
| `secondaryExpiresAt` | ISO 8601 oder null | Bis wann der vorige Ingest-Token nach einer Rotation noch gilt. |
| `createdAt`, `updatedAt` | ISO 8601 | Zeitstempel. |

```json
{
  "id": 7,
  "organizationId": 1,
  "teamId": 3,
  "name": "Zabbix production",
  "type": "zabbix",
  "secondaryExpiresAt": null,
  "payloadMapping": {
    "title": "trigger_name",
    "severity": "event_severity",
    "status": "trigger_status",
    "host": "host_name",
    "dedupKey": "event_id",
    "severityMap": { "Disaster": "sev1", "High": "sev2", "Average": "sev3", "Warning": "sev4", "Information": "sev4", "Not classified": "sev4" },
    "resolveValues": ["RESOLVED", "OK"]
  },
  "autoResolve": true,
  "groupingWindowMinutes": null,
  "maintenance": null,
  "snoozeUntil": null,
  "createdAt": "2026-10-01T08:00:00.000Z",
  "updatedAt": "2026-10-01T08:00:00.000Z",
  "authMode": {}
}
```

## Sources auflisten

`GET /api/im/sources`

Liefert alle Sources deiner Organisation als Array von Source-Objekten, sortiert nach Name.

```bash
curl "$BASE_URL/api/im/sources" \
  -H "Authorization: Bearer $TOKEN"
```

## Source abrufen

`GET /api/im/sources/:id`

Liefert ein Source-Objekt.

## Presets auflisten

`GET /api/im/sources/presets`

Liefert die eingebauten Presets, die du beim Anlegen als `type` übergeben kannst. `docsSlug` ist die Anleitung unter `/de/api/incident-management/alert-sources/`, `vendorTemplate` eine fertige Anbieter-Konfiguration (nur Zabbix bringt eine mit, ein Media-Type-YAML), `samplePayload` die Test-Payload, auf die [test-mapping](#mapping-testen) und [test-alert](#test-alert-auslösen) zurückfallen.

```json
[
  {
    "name": "zabbix",
    "docsSlug": "zabbix",
    "vendorTemplate": "zabbix_export:\n  version: '7.4'\n  ...",
    "samplePayload": { "event_id": "6633487", "event_severity": "Disaster", "trigger_name": "Zabbix agent is not available on Zabbix server", "trigger_status": "PROBLEM", "host_name": "Zabbix server" }
  },
  { "name": "datadog", "docsSlug": "datadog", "vendorTemplate": null, "samplePayload": { "alert_id": "1234567", "alert_title": "[Triggered] High CPU usage on host web-01" } }
]
```

Die vollständige Liste ist `zabbix`, `datadog`, `grafana`, `alertmanager`, `sentry`, `custom` (Beispiel-Payloads oben gekürzt).

## Source anlegen

`POST /api/im/sources`

### Request Body

| Feld | Typ | Erforderlich | Beschreibung |
|-------|------|----------|-------------|
| `teamId` | number | Ja | Team in deiner Organisation. |
| `name` | string | Ja | 1 bis 120 Zeichen, Leerraum wird abgeschnitten. |
| `type` | string | Ja | Ein Preset-Name, `api` (keine Ingest-URL, gespeist über [Events-Ingest](/de/api/incident-management/events-ingest)) oder `email` (eingehende Mail-Adresse). |
| `payloadMapping` | object | Nein | Weglassen, um mit dem Mapping des Presets zu starten. Ein expliziter Wert, auch `{}`, ersetzt das Preset. Bei `email` bleibt nur der verschachtelte Schlüssel `emailSelectors` erhalten, siehe [E-Mail-Anleitung](/de/api/incident-management/alert-sources/email). |
| `autoResolve` | boolean | Nein | Standard `true`. |
| `groupingWindowMinutes` | number oder null | Nein | Positive Ganzzahl oder `null` (Standard). |
| `authMode` | object | Nein | Hier werden nur `url_token` und `enabled: false` angenommen. HMAC und Bearer schaltest du über ihre eigenen Endpunkte weiter unten ein. |

`payloadMapping` gibt es in zwei Formen. Die flache Form nutzt String-Selektoren `title`, `severity`, `status`, `host`, `dedupKey`, dazu `severityMap` (Rohwert auf `sev1` .. `sev4`), `resolveValues` (Array von Strings) und `custom` (Objekt aus Selektoren). Die Attribut-Form ist `{ "version": 2, "attributes": [...] }`, jedes Attribut `{ key, standard?, steps?, flags? }` mit Schritten `{ kind: "extract", type: "path" | "jsonpath" | "constant", expr }` oder `{ kind: "valueMap", entries: [{ from, to }], fallback? }`. Beide Formen kennen `defaultSeverity` (`sev1` .. `sev4`, Standard `sev3`), das greift, wenn die Payload keinen gültigen Schweregrad liefert.

### Beispiel (cURL)

```bash
curl -X POST "$BASE_URL/api/im/sources" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "teamId": 3, "name": "Zabbix production", "type": "zabbix" }'
```

### Antwort (Response)

`200 OK`: das Source-Objekt plus `ingestToken`. **Der Token steht nur in dieser Antwort.** Gespeichert wird nur sein SHA-256-Hash, später lesen kannst du ihn nicht mehr. Die Webhook-URL lautet `https://uptimeify.io/api/im/ingest/<ingestToken>`. Bei `type: "api"` ist der Token `null`. Bei `type: "email"` enthält die Antwort zusätzlich `inboundEmailAddress` (`<token>@alerts.uptimeify.io`).

```json
{
  "id": 7,
  "organizationId": 1,
  "teamId": 3,
  "name": "Zabbix production",
  "type": "zabbix",
  "secondaryExpiresAt": null,
  "payloadMapping": { "title": "trigger_name", "dedupKey": "event_id" },
  "autoResolve": true,
  "groupingWindowMinutes": null,
  "maintenance": null,
  "snoozeUntil": null,
  "createdAt": "2026-10-01T08:00:00.000Z",
  "updatedAt": "2026-10-01T08:00:00.000Z",
  "authMode": {},
  "ingestToken": "Q2hR4mVx9LkP0sTz7bNw1cYe5uJa3fGd"
}
```

## Source ändern

`PATCH /api/im/sources/:id`

Schick nur die Felder, die du ändern willst. Die Schreibberechtigung wird am aktuellen Team geprüft, beim Verschieben zusätzlich am Zielteam.

| Feld | Typ | Beschreibung |
|-------|------|-------------|
| `name` | string | 1 bis 120 Zeichen. |
| `teamId` | number | Verschiebt die Source in ein anderes Team derselben Organisation. |
| `payloadMapping` | object | **Ersetzt** das ganze Mapping, es wird nicht zusammengeführt. `null` setzt ein leeres Mapping. |
| `authMode` | object | Wird mit dem gespeicherten Wert zusammengeführt. `hmac.enabled: true` / `bearer.enabled: true` gehen erst, wenn ein Secret existiert (siehe unten). `bearer.token_hash` wird abgelehnt. |
| `autoResolve` | boolean | |
| `groupingWindowMinutes` | number oder null | `null` entfernt das Fenster. |
| `maintenance` | object oder null | `{ "windows": [{ "dow": [1,2,3,4,5], "from": "22:00", "to": "23:30", "timezone": "Europe/Berlin" }] }`. `from`/`to` als 24-h-`HH:MM`, `dow` ISO-Wochentage 1 (Montag) bis 7, `timezone` eine IANA-Zone, höchstens 20 Fenster. `null` entfernt die Wartung. Alerts innerhalb eines Fensters werden unterdrückt. |
| `snoozeUntil` | ISO 8601 oder null | Schaltet die Source bis zu diesem Zeitpunkt stumm. Ein Wert in der Vergangenheit wird angenommen und wirkt nicht. `null` hebt es auf. |

Den Ingest-Token änderst du hier nicht, dafür gibt es [rotate-token](#ingest-token-rotieren).

```bash
curl -X PATCH "$BASE_URL/api/im/sources/7" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "snoozeUntil": "2026-10-09T18:00:00Z", "groupingWindowMinutes": 15 }'
```

`200 OK`: das geänderte Source-Objekt.

## Source löschen

`DELETE /api/im/sources/:id`

Wird mit `409` abgelehnt, solange die Source noch offene Alerts hat oder primäre Source eines offenen Incidents ist (alles, was nicht `resolved` oder `merged` ist). Löse diese zuerst. Die Monitoring-Brücke (`uptimeify_monitoring`) lässt sich nicht löschen.

`200 OK`: `{ "success": true }`.

Der Body der `409` (`imSourceReferencesExist`) nennt, was das Löschen blockiert (jede Liste höchstens 50 Einträge):

```json
{
  "statusCode": 409,
  "statusMessage": "Source is still referenced by open alerts or incidents",
  "data": {
    "code": "imSourceReferencesExist",
    "openAlertCount": 2,
    "openIncidents": [{ "id": "42", "title": "Database connection pool exhausted" }]
  }
}
```

## Ingest-Token rotieren

`POST /api/im/sources/:id/rotate-token`

Kein Request Body. Erzeugt einen neuen Ingest-Token. Der vorige Token gilt noch **7 Tage** weiter (bis `secondaryExpiresAt`), so kannst du die Konfiguration beim Anbieter umstellen, ohne Alerts zu verlieren. Der neue Token steht **nur in dieser Antwort**. Bei einer E-Mail-Source lautet die neue Eingangsadresse `<ingestToken>@alerts.uptimeify.io`.

```json
{
  "ingestToken": "Vn8kT1qZ4xLb7RwE0mYc2sHa9uJd6fPg",
  "secondaryExpiresAt": "2026-10-16T09:30:00.000Z"
}
```

## Optionale Absender-Authentifizierung

Der Token in der URL ist immer Pflicht. Zusätzlich kannst du eine Signatur oder einen Header verlangen:

- **HMAC**: Der Absender schickt `X-Uptimeify-Timestamp` (Unix-Sekunden, höchstens 5 Minuten Abweichung) und `X-Uptimeify-Signature`, das hex-codierte HMAC-SHA256 über `<timestamp>.<roher Body>` mit dem Secret der Source. Ein Präfix `sha256=` wird akzeptiert.
- **Bearer**: Der Absender schickt `Authorization: Bearer <token>`.

Eine Anfrage, die eine dieser Prüfungen nicht besteht, wird genau wie ein unbekannter Token beantwortet (`404`). `authMode.url_token` wird gespeichert, hat aber keine Wirkung.

### HMAC-Secret setzen

`POST /api/im/sources/:id/set-hmac-secret`

| Feld | Typ | Erforderlich | Beschreibung |
|-------|------|----------|-------------|
| `secret` | string | Nein | Dein eigenes Secret, mindestens 16 Zeichen. Weglassen, um ein 256-Bit-Secret erzeugen zu lassen. |

Schaltet HMAC ein (`authMode.hmac.enabled: true`). Ein weiterer Aufruf ersetzt das Secret, das alte gilt sofort nicht mehr. Das Secret wird verschlüsselt gespeichert und **nur in dieser Antwort zurückgegeben**:

```json
{ "secret": "c2VjcmV0LWV4YW1wbGUtbm90LXJlYWwtMTIzNDU2Nzg5MA", "hmacEnabled": true }
```

### Bearer-Token rotieren

`POST /api/im/sources/:id/rotate-bearer-token`

Kein Request Body. Erzeugt einen 256-Bit-Bearer-Token und schaltet Bearer-Authentifizierung ein. Ein vorheriger Bearer-Token gilt sofort nicht mehr. Gespeichert wird nur ein Hash, der Token steht **nur in dieser Antwort**:

```json
{ "token": "b3V0LW9mLWJhbmQtZXhhbXBsZS10b2tlbi1ub3QtcmVhbA", "bearerEnabled": true }
```

### HMAC oder Bearer abschalten

`POST /api/im/sources/:id/disable-auth`

| Feld | Typ | Erforderlich | Beschreibung |
|-------|------|----------|-------------|
| `mode` | string | Ja | `hmac` oder `bearer`. |

Setzt `authMode.<mode>.enabled` auf `false` und liefert das Source-Objekt. Das Secret bleibt gespeichert, daher schaltet `PATCH` mit `{ "authMode": { "hmac": { "enabled": true } } }` es ohne neues Secret wieder ein. Einen schon abgeschalteten Modus abzuschalten gelingt ebenfalls.

## Mapping testen

`POST /api/im/sources/:id/test-mapping`

Wendet das gespeicherte Mapping der Source auf eine Payload an, ohne etwas anzulegen. Reihenfolge der Payload: `payload` aus dem Body, sonst die zuletzt empfangene Payload der Source, sonst die Beispiel-Payload des Presets. Ein Mapping-Fehler ist kein HTTP-Fehler: Die Antwort ist `200` mit `ok: false`.

| Feld | Typ | Erforderlich | Beschreibung |
|-------|------|----------|-------------|
| `payload` | object | Nein | Die JSON-Payload, die gemappt werden soll. |

```bash
curl -X POST "$BASE_URL/api/im/sources/7/test-mapping" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{ "payload": { "event_id": "6633487", "event_severity": "Disaster", "trigger_name": "Zabbix agent is not available on Zabbix server", "trigger_status": "PROBLEM", "host_name": "Zabbix server" } }'
```

```json
{
  "ok": true,
  "alert": {
    "title": "Zabbix agent is not available on Zabbix server",
    "severity": "sev1",
    "status": "open",
    "host": "Zabbix server",
    "dedupKey": "6633487",
    "custom": {}
  },
  "attributes": [
    { "key": "title", "standard": true, "value": "Zabbix agent is not available on Zabbix server", "resolved": true, "flags": {} },
    { "key": "severity", "standard": true, "value": "sev1", "resolved": true, "flags": {} },
    { "key": "status", "standard": true, "value": "PROBLEM", "resolved": true, "flags": {} },
    { "key": "host", "standard": true, "value": "Zabbix server", "resolved": true, "flags": {} },
    { "key": "correlationId", "standard": true, "value": "6633487", "resolved": true, "flags": { "grouped": true } }
  ]
}
```

Bei einem Fehler: `{ "ok": false, "error": "title selector resolved to nothing on this payload" }`.

`alert.status` ist nur dann `resolved`, wenn der gemappte Statuswert nach `resolveValues` gleich `resolved` ist (ohne Beachtung der Groß-/Kleinschreibung), alles andere ist `open`. Ohne `dedupKey`-Selektor ist der Dedup-Key der SHA-1 aus Titel plus Host.

## Test-Alert auslösen

`POST /api/im/sources/:id/test-alert`

Stellt einen echten Alert in die normale Ingest-Pipeline. Meist öffnet das einen echten Incident und **alarmiert, wer für das Team der Source On-Call ist**. Payload: `payload` aus dem Body, sonst die Beispiel-Payload des Presets (kein Rückgriff auf zuletzt empfangene Payloads). Der Body ist wie bei der Ingest-URL auf 256 KB und 20 Verschachtelungsebenen begrenzt.

| Feld | Typ | Erforderlich | Beschreibung |
|-------|------|----------|-------------|
| `payload` | beliebiges JSON | Nein | Die Payload, die eingespielt wird. Pflicht bei `api`- und `email`-Sources, die keine Beispiel-Payload haben. |

`200 OK`: `{ "enqueued": true }`.

## Letzte Payloads

`GET /api/im/sources/:id/payloads`

Bis zu 10 zuletzt empfangene Roh-Payloads der Source, neueste zuerst. Hilfreich beim Bauen eines Mappings. Sie liegen in einem kurzlebigen Cache, eine leere Liste ist bei einer ruhigen Source normal.

```json
{ "payloads": [ { "event_id": "6633487", "trigger_status": "PROBLEM" } ] }
```

## Source-Statistik

`GET /api/im/sources/:id/stats`

Tageszähler der letzten 30 Tage mit Daten, älteste zuerst. `day` ist ein UTC-Kalendertag. `alerts` zählt eingehende Alerts, `deduped` die, die in einen schon offenen Alert eingeflossen sind, `incidents` die geöffneten Incidents.

```json
{
  "days": [
    { "day": "2026-10-07", "alerts": 41, "deduped": 33, "incidents": 3 },
    { "day": "2026-10-08", "alerts": 12, "deduped": 9, "incidents": 1 }
  ]
}
```

## Häufige Fehler

- `401 Unauthorized` (`unauthorized`) wenn du nicht authentifiziert bist
- `403 Forbidden` (`customerScopedTokenForbidden`) bei einem kunden-gescopten Token
- `403 Forbidden` (`imAccessDenied`) wenn deine Session keine IM-berechtigte Rolle hat
- `403 Forbidden` (`imNotEnabled`) wenn Incident Management für die Organisation nicht aktiviert ist
- `403 Forbidden` (`imTeamWriteDenied`) bei einem Schreib-Endpunkt ohne Rolle `admin` oder Team-Admin-Mitgliedschaft
- `400 Bad Request` (`invalidRequestBody`) wenn `:id` keine positive Ganzzahl ist, `teamId`/`name`/`type` fehlt oder ungültig ist, ein Mapping, eine Wartung oder `snoozeUntil` fehlerhaft ist, `authMode.bearer.token_hash` mitgeschickt wird, `mode` nicht `hmac`/`bearer` ist oder ein mitgegebenes HMAC-Secret kürzer als 16 Zeichen ist
- `404 Not Found` (`imSourceNotFound`) wenn die Source nicht existiert oder zu einer anderen Organisation gehört
- `422 Unprocessable Entity` (`invalidTeamId`) wenn `teamId` kein Team der Organisation ist
- `422 Unprocessable Entity` (`imAuthModeNotProvisionable`) wenn HMAC oder Bearer per Anlegen/`PATCH` eingeschaltet wird, bevor ein Secret existiert
- `409 Conflict` (`imSourceReferencesExist`) beim Löschen einer Source mit offenen Alerts oder Incidents
- `409 Conflict` (`imSourceProtected`) beim Löschen der Monitoring-Brücke
- `400 Bad Request` (`imSourceHasNoIngestToken`) beim Rotieren des Tokens einer `api`-Source
- `400 Bad Request` (`imNoTestPayloadAvailable`) wenn test-mapping oder test-alert keine Payload zur Verfügung hat
- `413 Payload Too Large` (`payloadTooLarge`), `422 Unprocessable Entity` (`invalidJson`, `payloadTooDeep`) bei test-alert
- `503 Service Unavailable` (`unavailable`) wenn test-alert den Alert nicht einreihen kann, sicher wiederholbar
