# API: Webhooks

Webhooks anlegen, Ereignisse empfangen und Signaturen prüfen.

## Hinweise für KI-Agenten

- API-Basisadresse: `https://api.eventwerk.eu/api/v1`
- OpenAPI 3.1: `https://api.eventwerk.eu/openapi.json` und `/api/v1/openapi.json`. Daraus lassen sich Clients und Typprüfungen generieren.
- Anmeldung: `Authorization: Bearer ew_...`
- Schlüssel: Einstellungen unter `/dashboard/einstellungen`, nur mit Eventwerk Plus und nur für Eigentümer. Der volle Schlüssel wird nur einmal angezeigt. Name und Zeitpunkt der letzten Nutzung werden geführt und ausgegeben.
- Zeiten: Alle Zeitpunkte kommen in UTC mit Suffix `Z`. Die Zeitzone des Events steht getrennt in `timezone` als IANA-Name, Standard `Europe/Vienna`. Clients rechnen selbst um.
- Paginierung: Die Event- und Anmeldelisten liefern `{items, has_more, next_cursor}` mit `limit` (Standard 50, höchstens 200), `cursor` und `updated_since`.
- Grenzen: 200 Anfragen pro Minute je Schlüssel. Der Zähler liegt im Speicher je Instanz, ist hinter mehreren Instanzen ein Vielfaches und beginnt nach einem Deploy neu. Bei Überschreitung antwortet die API mit 429, `rate_limited` und `Retry-After`.
- Gastgrenzen: Anmelde-Link 20 je Stunde je IP-Adresse, Anmeldung 60 je Stunde je IP-Adresse und 3 je Stunde je E-Mail-Adresse und Event.
- Fehler: JSON mit `code` und `message`, optional mit `details`.

Ein Webhook ist eine URL, an die Eventwerk bei einer neuen Benachrichtigung einen HTTP-POST sendet. Webhooks gehören zu Eventwerk Plus. Sie werden pro Organisation verwaltet.

## Webhooks verwalten

> **Warnung:** Die Webhook-Endpunkte erwarten ein Sitzungs-Token aus der E-Mail-Anmeldung, keinen API-Schlüssel. Sie sind nur für Eigentümer im Plus-Plan freigegeben.

| Methode | Pfad | Antwort |
| --- | --- | --- |
| GET | /api/webhooks | Liste der aktiven Webhooks. |
| POST | /api/webhooks | 201 mit dem angelegten Webhook. |
| DELETE | /api/webhooks/{webhookID} | 204 ohne Rumpf. |

Ein Webhook enthält `id`, `url`, `secret`, `created_at` und `revoked_at`. Die Liste zeigt nur nicht widerrufene Webhooks. Das Geheimnis steht in der Antwort und dient der Signaturprüfung.

```bash
curl -s -X POST /api/webhooks \
  -H "Authorization: Bearer <Access-Token>" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://example.test/eventwerk-hook"}'
```

- Die URL muss mit http oder https beginnen und höchstens 2.048 Zeichen lang sein.
- Lokale Adressen sind erlaubt, damit sich die Zustellung testen lässt.
- Eine ungültige URL ergibt `invalid_webhook_url`.
- Ein unbekannter oder fremder Webhook ergibt `webhook_not_found`.
- Ohne Plus antwortet die Verwaltung mit `plan_required`.

## Zustellung

Eventwerk sendet einen POST mit JSON-Rumpf. Der Rumpf enthält die Art, den Titel, den Text, den internen Link und den Zeitpunkt der Benachrichtigung.

```json
{
  "kind": "registration_created",
  "title": "Neue Anmeldung",
  "body": "Anna hat sich für Herbstkonferenz angemeldet.",
  "link_path": "/dashboard/events/<eventID>/registrations",
  "created_at": "2026-10-25T12:00:00Z"
}
```

| Header | Wert |
| --- | --- |
| Content-Type | application/json |
| User-Agent | Eventwerk-Webhook/1.0 |
| X-Eventwerk-Signature | `sha256=<HMAC-SHA256 des unveränderten Rumpfs mit dem Webhook-Geheimnis>` |

- Ein Zustellversuch hat ein Zeitlimit von 5 Sekunden.
- Jeder Webhook erhält höchstens zwei Versuche im Abstand von 500 Millisekunden.
- Eine Antwort mit Status 2xx gilt als erfolgreich.
- Es gibt keine dauerhafte Warteschlange. Nach einem Neustart gehen nicht zugestellte Aufrufe verloren.
- Fehler werden nur protokolliert. Der auslösende Vorgang, etwa eine Anmeldung, bleibt gültig.
- Die Reihenfolge der Zustellungen ist nicht garantiert.

## Signatur prüfen

Berechnen Sie die HMAC-SHA256-Signatur über den rohen Rumpf, nicht über das neu serialisierte JSON. Vergleichen Sie die Signatur in konstanter Zeit mit dem Header.

```javascript
import crypto from "node:crypto";

const expected =
  "sha256=" +
  crypto.createHmac("sha256", secret).update(rawBody).digest("hex");

const received = request.headers["x-eventwerk-signature"];

if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received))) {
  return response.status(401).end();
}
```

## Ereignisse

| kind | Auslöser |
| --- | --- |
| registration_created | Neue öffentliche Anmeldung. |
| registration_cancelled | Anmeldung storniert. |
| registration_promoted | Anmeldung von der Warteliste nachgerückt. |
| registration_checked_in | Anmeldung eingecheckt. |
| survey_response | Neue Umfrageantwort. |
| message_sent | Nachricht versandt. |

> **Warnung:** Eine eigene Oberfläche für Webhooks gibt es noch nicht. Die Verwaltung läuft über die API. Zustellungen werden nicht gespeichert und nicht nachträglich nachgeholt.
