
# Webhook für das Changelog abonnieren

`POST https://api.keenauth.com/v1/accounts/{account_id}/changelog-subscriptions` · Kontoinhaber oder Administrator

Registriert einen eigenen HTTPS-Endpunkt, der einen `changelog.entry_published`-Webhook empfängt, sobald ein [Changelog](/docs/changelog)-Eintrag in einer der gewählten Kategorien veröffentlicht wird. Die Anfrage wird mit den Zugangsdaten von `keenauth login` gestellt: Das Konto muss dasjenige sein, bei dem Sie angemeldet sind, und Sie müssen dessen Inhaber oder Administrator sein. Ein Konto kann höchstens 5 aktivierte Abonnements haben.

Jedes Abonnement hat ein eigenes Signaturgeheimnis, das in der Antwort einmalig zurückgegeben wird. Die Zustellung folgt Standard Webhooks: Jede Anfrage enthält die Header `webhook-id`, `webhook-timestamp` und `webhook-signature`, und die Signatur (`v1,<base64>`) ist ein HMAC-SHA256 über `<webhook-id>.<webhook-timestamp>.<body>`. Der HMAC-Schlüssel sind die 32 Bytes, die die 43 Zeichen nach dem Präfix `idp_whsec_` kodieren (Base62, Big-Endian); die letzten 6 Zeichen sind eine Prüfsumme und gehören nicht zum Schlüssel. Prüfen Sie die Signatur und lehnen Sie einen Zeitstempel ab, der zu weit von der aktuellen Zeit entfernt ist (die Standard-Webhooks-Bibliotheken erlauben 5 Minuten), bevor Sie dem Inhalt vertrauen.

Legen Sie keine personenbezogenen Daten, Zugangsdaten oder Token in die URL: Sie wird mit dem Abonnement gespeichert und allen Inhabern und Administratoren des Kontos angezeigt.

## Anfrage

Header:

| Header | Pflicht | Beschreibung |
| --- | --- | --- |
| `Authorization` | ja | `DPoP <access token>`: das Zugriffstoken, das die CLI beim Anmelden erhalten hat. |
| `DPoP` | ja | Ein DPoP-Nachweis für diese Anfrage (`htm` `POST`, `htu` die URL dieser Anfrage), signiert mit dem Schlüssel, an den das Zugriffstoken gebunden ist; er enthält den Hash des Zugriffstokens und den Server-Nonce. |
| `Content-Type` | ja | `application/json`. |
| `Idempotency-Key` | nein | 16 bis 64 Zeichen aus `A-Z`, `a-z`, `0-9`, `_` und `-`. Wiederholt man die Anfrage mit demselben Schlüssel und Inhalt innerhalb von 24 Stunden, wird dasselbe Abonnement ohne das Geheimnis zurückgegeben, mit `secret_redacted: true` und dem Header `Idempotent-Replayed: true`. |

Anfragetext:

| Feld | Pflicht | Beschreibung |
| --- | --- | --- |
| `url` | ja | Der Endpunkt: `https`, Port 443, ein Hostname (keine IP-Adresse und kein nur lokal gültiger Name wie `.localhost`, `.local` oder `.internal`), ohne Benutzerinformationen oder Fragment, höchstens 2048 Byte. |
| `categories` | nein | Beliebige der Werte `api`, `sdk`, `deprecation`, `security` und `incident`, jeweils höchstens einmal. Lassen Sie das Feld weg, um alle Kategorien zu erhalten; ein leeres Array wird abgelehnt. |

## Antwort

`201 Created` mit `Cache-Control: no-store`:

| Feld | Beschreibung |
| --- | --- |
| `id` | Das Abonnement (`chlg_…`). |
| `account_id` | Das Konto (`acct_…`). |
| `url` | Der Endpunkt. |
| `categories` | Die empfangenen Kategorien in der Reihenfolge `api`, `sdk`, `deprecation`, `security`, `incident`. |
| `status` | `enabled`, oder `disabled`, wenn Zustellungen wiederholt fehlschlagen. |
| `created_at` | Zeitpunkt der Erstellung des Abonnements (RFC 3339, UTC). |
| `signing_secret` | Das Signaturgeheimnis (`idp_whsec_…`). Wird nur in dieser Antwort zurückgegeben; speichern Sie es in Ihrem Secret-Manager. |
| `secret_redacted` | `true` bei einer idempotenten Wiederholung, die kein `signing_secret` enthält. |

## Fehler

| Status | Code | Wann |
| --- | --- | --- |
| 400 | `malformed_request` | Der Anfragetext ist kein JSON, oder der Header `Idempotency-Key` ist mehrfach vorhanden, leer oder fehlerhaft. |
| 400 | `unknown_field` | Der Anfragetext enthält ein anderes Feld als `url` und `categories`. |
| 401 | `unauthenticated` | Es wurde kein Zugriffstoken gesendet. |
| 401 | `invalid_token` | Das Zugriffstoken ist ungültig, abgelaufen oder gehört nicht zur CLI; melden Sie sich erneut mit `keenauth login` an. |
| 401 | `invalid_dpop_proof` | Der DPoP-Nachweis fehlt, ist mehrfach vorhanden oder passt weder zur Anfrage noch zum Schlüssel des Tokens. |
| 401 | `use_dpop_nonce` | Wiederholen Sie die Anfrage mit einem Nachweis, der den Nonce aus dem Antwortheader `DPoP-Nonce` enthält. |
| 401 | `insufficient_user_authentication` | Multi-Faktor-Authentifizierung (bzw. für Inhaber und Administratoren ein Passkey oder Sicherheitsschlüssel) ist erforderlich; melden Sie sich erneut an. |
| 403 | `insufficient_permissions` | Sie sind kein Inhaber und kein Administrator des Kontos. |
| 403 | `account_restricted` | Der Status des Kontos erlaubt keine Änderungen. |
| 403 | `quota_exceeded` | Das Konto hat bereits 5 aktivierte Abonnements (`errors[].code` `changelog_subscription_limit_exceeded`). |
| 404 | `not_found` | Das Konto existiert nicht, ist nicht das Konto, bei dem Sie angemeldet sind, oder Sie sind dort kein Mitglied. |
| 413 | `payload_too_large` | Der Anfragetext ist größer als 8 KiB. |
| 415 | `unsupported_media_type` | Der Anfragetext ist nicht `application/json`. |
| 422 | `validation_failed` | `url` fehlt oder ist ungültig, eine Kategorie ist unbekannt oder mehrfach angegeben, `categories` ist leer, oder ein Feld hat den falschen Typ. |
| 422 | `idempotency_key_reuse` | Der `Idempotency-Key` wurde innerhalb von 24 Stunden mit einem anderen Anfragetext verwendet. |
| 429 | `rate_limited` | In der letzten Stunde wurden für das Konto mehr als 10 Abonnements erstellt; warten Sie `Retry-After` ab. |

## Webhook-Zustellung

Wenn ein Eintrag veröffentlicht wird, sendet KeenAuth einen `POST` an die URL jedes aktivierten Abonnements, dessen Kategorien eine der Kategorien des Eintrags enthalten. Derselbe Eintrag wird einem Abonnement nie zweimal gesendet.

### Anfrage

| Header | Beschreibung |
| --- | --- |
| `Content-Type` | `application/json`. |
| `webhook-id` | Die Ereigniskennung `id` (`evt_…`). Sie ist bei jeder Wiederholung desselben Eintrags gleich, verwenden Sie sie daher, um Duplikate zu ignorieren. |
| `webhook-timestamp` | Unix-Zeit in Sekunden, zu der dieser Versuch signiert wurde. Sie ist bei jedem Versuch neu. |
| `webhook-signature` | `v1,<base64>`: der oben beschriebene HMAC-SHA256 über `<webhook-id>.<webhook-timestamp>.<body>`. |

Der Anfragetext ist höchstens 16 KiB groß. KeenAuth folgt keinen Weiterleitungen.

### Ereignis

```json
{
  "id": "evt_01jb0000000000000000000000",
  "type": "changelog.entry_published",
  "schema_version": 1,
  "occurred_at": "2026-11-04T00:00:00Z",
  "recorded_at": "2026-11-04T08:30:12Z",
  "data": {
    "id": "2026-11-04-example",
    "url": "https://docs.keenauth.com/en/changelog/#2026-11-04-example",
    "title": "Example entry",
    "date_published": "2026-11-04T00:00:00Z",
    "tags": ["deprecation"],
    "categories": ["api", "deprecation"],
    "surfaces": [{"kind": "api", "name": "management-v1", "version": "v1"}],
    "breaking": false,
    "sunset_at": "2027-11-04"
  }
}
```

| Feld | Beschreibung |
| --- | --- |
| `id` | Das Ereignis (`evt_…`), identisch mit dem Header `webhook-id`. |
| `type` | Immer `changelog.entry_published`. |
| `schema_version` | Immer `1`. Innerhalb von Version 1 können `data` neue optionale Felder hinzugefügt werden; ignorieren Sie unbekannte Felder. |
| `occurred_at` | Zeitpunkt der Veröffentlichung des Eintrags (RFC 3339). |
| `recorded_at` | Zeitpunkt, zu dem KeenAuth die Zustellung eingereiht hat (RFC 3339). |
| `data.id`, `data.url`, `data.title`, `data.date_published` | Der Eintrag, wie in den Changelog-Feeds. |
| `data.tags` | Ein oder mehrere der Werte `added`, `changed`, `fixed`, `removed`, `deprecation`, `security`, `breaking` und `incident`. |
| `data.categories` | Die Abonnement-Kategorien, zu denen der Eintrag gehört. |
| `data.surfaces` | Die betroffenen Oberflächen: `kind` (`api`, `sdk`, `cli`, `provider`, `mcp` oder `docs`), `name` und, sofern bekannt, `version`. |
| `data.breaking` | `true`, wenn die Änderung inkompatibel ist. |
| `data.sunset_at` | Das Entfernungsdatum einer Veralterung (`YYYY-MM-DD`), andernfalls `null`. |

Das maschinenlesbare Schema ist `changelog.entry_published.v1`.

### Antworten und Wiederholungen

Antworten Sie innerhalb von 15 Sekunden mit einem beliebigen `2xx`-Status. Der Antworttext wird ignoriert. Jedes andere Ergebnis ist ein fehlgeschlagener Versuch und wird wiederholt: ein Verbindungs- oder TLS-Fehler, ein Timeout, eine Weiterleitung, `429`, `5xx` und jeder andere Status außer `410`. Der erste Versuch erfolgt sofort; die folgenden etwa 1 Minute, 5 Minuten, 30 Minuten, 2 Stunden, 8 Stunden, 24 Stunden und 36 Stunden nach dem vorherigen Fehlschlag (jedes Intervall variiert um bis zu 10 Prozent), insgesamt 8 Versuche in etwa 3 Tagen.

Schlägt der achte Versuch fehl, wird die Zustellung aufgegeben und das Abonnement deaktiviert (`status` `disabled`). Bereits für das Abonnement eingereihte Zustellungen werden abgebrochen.

### 410 Gone

Antworten Sie mit `410 Gone`, um KeenAuth mitzuteilen, dass Sie keine Zustellungen mehr wünschen. Das Abonnement wird sofort ohne weitere Versuche deaktiviert, und seine eingereihten Zustellungen werden abgebrochen. Ein deaktiviertes Abonnement wird nicht wieder aktiviert und zählt nicht zum Limit von 5; erstellen Sie ein neues, um wieder Einträge zu erhalten. Jede Deaktivierung wird im Audit-Log des Kontos als `changelog.subscription_disabled` festgehalten, mit dem Grund `receiver_gone`, `permanent_failure` oder `schedule_exhausted`.

## Beispiel

```bash
curl -X POST https://api.keenauth.com/v1/accounts/acct_01jb0000000000000000000000/changelog-subscriptions \
  -H "Authorization: DPoP $ACCESS_TOKEN" \
  -H "DPoP: $DPOP_PROOF" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: changelog-hooks-2026-10" \
  -d '{"url": "https://hooks.example.com/keenauth/changelog", "categories": ["deprecation", "security"]}'
```
