Payload-Schemas, Signaturen und Retry-Verhalten
Webhooks wurden jahrelang außerhalb der API-Dokumentation beschrieben – in separaten Markdown-Dateien, die niemand aktuell hält. OpenAPI 3.1 bringt das neue webhooks-Objekt, das Webhook-Payload-Schemas, Sicherheitsanforderungen und Retry-Verhalten direkt in der Spezifikation verankert.
Inhaltsverzeichnis
- 1. Das Problem: Webhooks außerhalb der API-Dokumentation
- 2. OpenAPI 3.1: Was sich geändert hat
- 3. Das webhooks-Objekt: Aufbau und Syntax
- 4. Payload-Schemas mit Discriminator
- 5. HMAC-Signatur und Sicherheit dokumentieren
- 6. Retry-Verhalten und Idempotenz
- 7. Tool-Unterstützung: Redoc, Swagger UI, Stoplight
- 8. OpenAPI 3.0 vs. 3.1 für Webhooks im Vergleich
- 9. Zusammenfassung
- 10. FAQ
1. Das Problem: Webhooks außerhalb der API-Dokumentation
Vor OpenAPI 3.1 war die Dokumentation von Webhooks ein gelöstes Problem ohne Standard. Die typische Lösung: ein separates Markdown-Dokument mit JSON-Beispielen, das manuell synchron mit dem tatsächlichen Webhook-Payload gehalten werden musste. Das Ergebnis in der Praxis: Payload-Schemas driften von der Realität ab, Entwickler verlassen sich auf den eigentlichen Request statt auf die Dokumentation, und jede Änderung am Webhook erfordert Updates an zwei getrennten Stellen.
OpenAPI 3.0 hatte eine inoffizielle Praxis: Webhooks als Callbacks oder als eigene Path-Einträge mit einem Prefix wie /webhooks/order-created zu dokumentieren. Das ist technisch ungenau, weil Webhooks keine API-Endpunkte des Servers sind, sondern HTTP-Anfragen des Servers an den Client. OpenAPI 3.1 löst das sauber mit dem webhooks-Objekt auf derselben Ebene wie paths – eine eigene Sektion für ausgehende Server-zu-Client-Kommunikation.
2. OpenAPI 3.1: Was sich geändert hat
OpenAPI 3.1.0 wurde im Februar 2021 veröffentlicht und bringt mehrere relevante Neuerungen neben dem webhooks-Objekt. Die Spezifikation ist jetzt vollständig JSON-Schema-kompatibel (Draft 2020-12), was bedeutet, dass alle JSON-Schema-Keywords direkt verwendet werden können, ohne Kompatibilitäts-Workarounds. Das ist besonders für Webhook-Payload-Schemas wichtig, wo if/then/else-Bedingungen, unevaluatedProperties und $dynamicRef nutzbar werden.
Weitere 3.1-Neuerungen mit Auswirkung auf Webhook-Dokumentation: pathItems können jetzt in components definiert und per $ref in webhooks und paths wiederverwendet werden. Das erlaubt, gemeinsame Webhook-Strukturen einmal zu definieren, auch wenn mehrere Ereignistypen denselben Base-Payload teilen. Und: license.identifier als SPDX-Identifier ist jetzt offiziell unterstützt – kein großes Feature, aber hilfreich für public API-Spezifikationen.
# openapi.yaml — Top-Level-Struktur mit webhooks in OpenAPI 3.1
openapi: 3.1.0
info:
title: Mironsoft Order API
version: 2.0.0
description: |
REST API für das Order-Management.
## Webhooks
Diese API sendet Webhook-Events an konfigurierte Endpunkte.
Alle Events sind unter `webhooks` dokumentiert.
Signaturprüfung: siehe `X-Mironsoft-Signature` Header.
servers:
- url: https://api.mironsoft.de/v2
# Webhook-Events auf derselben Ebene wie paths
webhooks:
order.created:
post:
summary: Order Created Event
description: |
Wird ausgelöst, wenn eine neue Bestellung erstellt wurde.
Der Receiver muss HTTP 200 zurückgeben.
requestBody:
required: true
content:
application/json:
schema:
$ref: '#/components/schemas/OrderCreatedEvent'
responses:
'200':
description: Webhook erfolgreich empfangen
'4XX':
description: Fehler beim Empfang — wird gemäß Retry-Policy erneut gesendet
paths:
/webhooks/subscriptions:
post:
summary: Webhook-Subscription registrieren
# ...
3. Das webhooks-Objekt: Aufbau und Syntax
Das webhooks-Objekt in OpenAPI 3.1 ist ein Map-Objekt: Schlüssel sind frei wählbare Webhook-Namen (empfohlen: Event-Namen wie order.created, payment.failed), Werte sind Path-Item-Objekte – dieselbe Struktur wie unter paths. Das bedeutet, die gesamte Known-Vocabulary von Path-Items ist verfügbar: HTTP-Methoden, requestBody, responses, parameters und security.
Die HTTP-Methode für Webhooks ist fast immer post. Der requestBody beschreibt den Payload, den der Server an den Consumer sendet. Die responses beschreiben, was der Server vom Consumer erwartet – typischerweise 200 OK als Bestätigung, optional 202 Accepted wenn die Verarbeitung asynchron erfolgt. Diese Perspektivumkehr ist der konzeptionelle Unterschied zu normalen Paths: Im Webhook-Kontext ist der Server der Requester und der Consumer der Responder.
4. Payload-Schemas mit Discriminator
Wenn eine API mehrere Webhook-Event-Typen sendet, die einen gemeinsamen Basis-Payload teilen, ist der OpenAPI-discriminator das richtige Werkzeug. Ein gemeinsames Feld wie event_type oder type unterscheidet die Event-Varianten. Code-Generatoren können daraus typsichere Union-Typen oder sealed classes generieren. Ohne Discriminator müsste der Consumer den Payload manuell parsen und das Typ-Feld auswerten, ohne Schema-Unterstützung.
Die JSON-Schema-Kompatibilität in OpenAPI 3.1 erlaubt auch oneOf mit if/then für komplexere Payload-Strukturen, bei denen der Discriminator-Ansatz nicht ausreicht. Für die meisten Anwendungsfälle ist oneOf mit Discriminator die klarste und tool-freundlichste Variante.
# components/schemas in openapi.yaml
components:
schemas:
# Base event schema — shared by all webhook events
WebhookEvent:
type: object
required: [event_type, event_id, occurred_at]
properties:
event_type:
type: string
description: Discriminator field — identifies the event variant
example: "order.created"
event_id:
type: string
format: uuid
description: Unique event ID for idempotency checks
occurred_at:
type: string
format: date-time
api_version:
type: string
example: "2.0.0"
OrderCreatedEvent:
allOf:
- $ref: '#/components/schemas/WebhookEvent'
- type: object
required: [order]
properties:
order:
$ref: '#/components/schemas/OrderPayload'
OrderCancelledEvent:
allOf:
- $ref: '#/components/schemas/WebhookEvent'
- type: object
required: [order, cancellation_reason]
properties:
order:
$ref: '#/components/schemas/OrderPayload'
cancellation_reason:
type: string
enum: [customer_request, inventory, payment_failed, fraud]
# Discriminated union for all webhook event types
AnyWebhookEvent:
oneOf:
- $ref: '#/components/schemas/OrderCreatedEvent'
- $ref: '#/components/schemas/OrderCancelledEvent'
discriminator:
propertyName: event_type
mapping:
"order.created": '#/components/schemas/OrderCreatedEvent'
"order.cancelled": '#/components/schemas/OrderCancelledEvent'
5. HMAC-Signatur und Sicherheit dokumentieren
Webhook-Sicherheit ist in der API-Dokumentation oft unterrepräsentiert. Der verbreitetste Ansatz ist eine HMAC-SHA256-Signatur im Header: Der Server signiert den Request-Body mit einem geteilten Secret und schickt die Signatur als Header mit (X-Mironsoft-Signature: sha256=abc123...). Der Consumer verifiziert die Signatur, bevor er den Payload verarbeitet. Ohne diese Verifikation kann jeder beliebige HTTP-Client einen Webhook fälschen.
In OpenAPI 3.1 lässt sich das HMAC-Pattern als eigenes Security-Scheme dokumentieren. Da OAuth2 und API-Keys für Webhooks nicht passend sind, wird ein Custom-Header-Security-Scheme genutzt. Zusätzlich sollte die Dokumentation beschreiben, wie die Signatur berechnet wird: Algorithmus, Header-Format und ein Code-Beispiel in mindestens einer gängigen Sprache. Viele Consumer-Entwickler kopieren dieses Beispiel direkt in ihre Implementierung.
# Security scheme für HMAC-Webhook-Signatur
components:
securitySchemes:
webhookSignature:
type: apiKey
in: header
name: X-Mironsoft-Signature
description: |
HMAC-SHA256-Signatur des Request-Bodys.
**Format:** `sha256=<hex-digest>`
**Verifikation (PHP):**
```php
$signature = hash_hmac('sha256', $rawBody, $webhookSecret);
$expected = 'sha256=' . $signature;
if (!hash_equals($expected, $_SERVER['HTTP_X_MIRONSOFT_SIGNATURE'])) {
http_response_code(401);
exit;
}
```
**Verifikation (Python):**
```python
import hmac, hashlib
expected = 'sha256=' + hmac.new(
secret.encode(), body, hashlib.sha256
).hexdigest()
assert hmac.compare_digest(expected, request.headers['X-Mironsoft-Signature'])
```
# Zusätzliche Webhook-spezifische Header
headers:
X-Mironsoft-Event-Id:
description: Unique event ID — use for idempotency
schema:
type: string
format: uuid
X-Mironsoft-Retry-Attempt:
description: Retry attempt number (0 = first delivery)
schema:
type: integer
minimum: 0
X-Mironsoft-Delivery-Timestamp:
description: Unix timestamp of the delivery attempt
schema:
type: integer
# Webhook mit Sicherheitsanforderung
webhooks:
order.created:
post:
security:
- webhookSignature: []
# ...
6. Retry-Verhalten und Idempotenz
Webhook-Retry-Verhalten gehört zu den am häufigsten undokumentierten Aspekten von Webhook-Systemen. Wenn der Consumer-Endpunkt nicht erreichbar ist oder einen nicht-2xx-Status zurückgibt, sollte der Server den Webhook erneut senden. Ohne klare Dokumentation des Retry-Verhaltens bauen Consumer-Entwickler keine Idempotenz-Logik ein – und bei einem Retry wird die Bestellung zweimal verarbeitet.
In der OpenAPI-Spezifikation lässt sich das Retry-Verhalten in der Webhook-Beschreibung und in einem eigenen x-webhook-delivery-Extension-Objekt dokumentieren. Das ist kein Standard-Keyword, aber Tools wie Redoc zeigen x--Extensions in der Dokumentation an, wenn sie entsprechend konfiguriert sind. Das Wichtigste ist, dass die Dokumentation die Antwort auf drei Fragen gibt: Wann wird ein Retry ausgelöst, wie viele Retries gibt es, und wie soll der Consumer Idempotenz sicherstellen.
7. Tool-Unterstützung: Redoc, Swagger UI, Stoplight
Redoc unterstützt webhooks ab Version 2.1.0 nativ und zeigt sie als eigene Sektion in der Seitenleiste an. Swagger UI unterstützt Webhooks ab Version 5.0.0 (mit eingeschränkter Darstellung). Stoplight Elements unterstützt OpenAPI 3.1 Webhooks vollständig. API-Clients wie Postman können Webhook-Schemas aus OpenAPI 3.1 importieren, um Consumer-Tests zu schreiben.
Code-Generatoren wie openapi-generator-cli unterstützen Webhook-Schemas für die meisten Zielsprachen seit Version 6.x. Die generierten Klassen können direkt als Payload-Typ im Consumer-Code verwendet werden – inklusive Discriminator-Mapping für Event-Unions. Das reduziert manuelles Schreiben von Parsing-Code erheblich.
| Tool | OAS 3.1 webhooks | Discriminator | Code-Gen | Ab Version |
|---|---|---|---|---|
| Redoc | Vollständig | Ja | Nein | 2.1.0 |
| Swagger UI | Teilweise | Teilweise | Nein | 5.0.0 |
| Stoplight Elements | Vollständig | Ja | Nein | 8.0.0 |
| openapi-generator-cli | Ja | Ja | Ja | 6.0.0 |
| Postman | Import | Teilweise | Nein | 10.x |
Mironsoft
OpenAPI 3.1, Webhook-Systeme und API-Dokumentation
OpenAPI 3.1 Webhook-Dokumentation aufsetzen?
Wir migrieren eure API-Spezifikation auf OpenAPI 3.1, dokumentieren Webhooks mit vollständigen Payload-Schemas und HMAC-Sicherheitsdokumentation und integrieren alles in Redoc oder Stoplight Elements.
OAS 3.1 Migration
Migration bestehender openapi.yaml von 3.0 auf 3.1 mit JSON-Schema-Kompatibilität
Webhook-Schemas
Payload-Schemas mit Discriminator, HMAC-Dokumentation und Retry-Beschreibung
Tool-Integration
Redoc, Stoplight Elements oder Swagger UI aufsetzen und in CI-Pipeline integrieren
8. OpenAPI 3.0 vs. 3.1 für Webhooks im Vergleich
Der Unterschied zwischen OpenAPI 3.0 und 3.1 ist für Webhook-Dokumentation besonders relevant. In 3.0 war die beste verfügbare Option, Webhooks als Callbacks eines Subscription-Endpunkts zu dokumentieren oder sie unter eigene paths zu packen – beides konzeptuell ungenau. OpenAPI 3.1 löst das mit dem dedizierten webhooks-Objekt und macht Webhooks zu einem First-Class-Citizen der Spezifikation.
9. Zusammenfassung
OpenAPI 3.1 bringt mit dem webhooks-Objekt die lang fehlende Standardlösung für Webhook-Dokumentation. Payload-Schemas mit oneOf und Discriminator ermöglichen typsichere Consumer-Implementierungen und maschinenlesbare Event-Kataloge. HMAC-Signatur-Dokumentation als Security-Scheme, Retry-Verhalten in der Event-Beschreibung und Tool-Unterstützung in Redoc und Stoplight machen Webhooks zum vollwertigen Teil der API-Spezifikation.
Der wichtigste Schritt: Webhook-Events mit eindeutigen Namen (order.created, payment.failed) und vollständigen Payload-Schemas versehen. Ein event_id-Feld in jedem Payload ermöglicht Idempotenz-Checks auf Consumer-Seite. Und: Retry-Verhalten immer in der Dokumentation beschreiben, damit Consumer-Entwickler von Anfang an Idempotenz-Logik einbauen.
Webhooks in OpenAPI 3.1 — Das Wichtigste auf einen Blick
webhooks-Objekt
Auf derselben Ebene wie paths. Schlüssel: Event-Namen. Werte: Path-Item-Objekte mit requestBody, responses und security.
Payload-Schemas
oneOf + Discriminator für Event-Unions. JSON-Schema-Kompatibilität in OAS 3.1 ermöglicht if/then/else für komplexe Payloads.
HMAC-Signatur
Als apiKey-Security-Scheme dokumentieren. Code-Beispiele für PHP und Python in der description. hash_equals() für timing-sichere Verifikation.
Idempotenz
event_id als UUID in jedem Payload. Consumer speichert verarbeitete IDs. Retry-Verhalten und Retry-Intervalle immer dokumentieren.