Architektur
ArchitekturFortgeschritten

Webhooks erklärt: Wie sie funktionieren, wie man sie absichert und skaliert

Was ein Webhook ist, wie er sich von API-Polling und WebSockets unterscheidet, wie Stripe, GitHub, Shopify und Slack ihre Zustellungen signieren und wiederholen, wie Sie HMAC-Signaturen ohne den klassischen Raw-Body-Fehler prüfen und wie Sie Webhooks zuverlässig empfangen und versenden.

Titelbild: Webhooks erklärt: Wie sie funktionieren, wie man sie absichert und skaliert
Inhalt

Jede Integration, die Sie je gebaut haben, stellt irgendwann dieselbe Frage: Wie erfahre ich, dass sich auf der anderen Seite etwas geändert hat? Sie können jede Minute nachfragen — oder Sie geben der anderen Seite eine URL und lassen sie Bescheid sagen. Die zweite Variante ist ein Webhook. So meldet Stripe eine erfolgreiche Zahlung, GitHub Ihrer CI einen neuen Push und Shopify Ihrem Lager eine neue Bestellung.

Webhooks wirken trivial: ein HTTP-POST mit etwas JSON. Genau deshalb scheitern so viele Implementierungen in der Produktion. Die Anfrage kann gefälscht sein, sie kann doppelt kommen, in falscher Reihenfolge, drei Tage zu spät — und wenn Ihr Handler vier Sekunden für die Antwort braucht, senden manche Anbieter erneut, während Sie die erste noch verarbeiten. Dieser Leitfaden zeigt das ganze Bild: was Webhooks sind, wie die großen Anbieter sie tatsächlich zustellen und was es braucht, um sie zu empfangen und zu versenden, ohne Ereignisse zu verlieren oder dem falschen Absender zu vertrauen.

Webhooks erklärt: ein signierter HTTP-POST, Retries mit wachsenden Abständen und eine ID, die Duplikate harmlos macht.

Was ist ein Webhook?

Ein Webhook ist eine HTTP-Anfrage, die ein Dienst an eine von Ihnen registrierte URL sendet, sobald ein abonniertes Ereignis eintritt. Mehr nicht. Die Spezifikation Standard Webhooks nennt sie einen „gängigen Namen für HTTP-Callbacks“ und beschreibt sie als eine Art umgekehrte API: Bei einer API rufen Sie den Dienst auf, beim Webhook ruft der Dienst Sie auf.

Eine typische Zustellung sieht so aus:

POST /webhooks/billing HTTP/1.1
Host: api.example.com
Content-Type: application/json
webhook-id: msg_2KWPBgLlAfxdpx2AI54pPJ85f4W
webhook-timestamp: 1674087231
webhook-signature: v1,K5oZfzN95Z9UVu1EsfQmfVNQhnkZ2pj9o9NDN/H/pI4=
{"type":"invoice.paid","timestamp":"2022-11-03T20:26:10.344522Z","data":{"id":"inv_123"}}

Drei Teile zählen:

  • Die Webhook-URL — der Endpunkt, den Sie beim Anbieter registriert haben, zum Beispiel https://api.example.com/webhooks/billing. In der Produktion muss sie öffentlich per HTTPS erreichbar sein.
  • Die Payload — ein JSON-Body mit dem Ereignistyp (invoice.paid), dem Zeitpunkt und den Daten.
  • Die Header — eine eindeutige ID für diese Nachricht, ein Zeitstempel und eine Signatur, die beweist, dass der Anbieter sie gesendet hat.

Ihr Endpunkt antwortet mit einem 2xx-Statuscode und sagt damit: erhalten. Alles andere — ein 500, ein Timeout, sogar eine Weiterleitung — gilt als fehlgeschlagene Zustellung, und der Anbieter versucht es meist erneut.

Vorab eine Namensfalle: Kubernetes kennt ebenfalls Admission Webhooks, die der API-Server aufruft, um Objekte vor dem Speichern zu prüfen oder zu verändern. Sie nutzen dieselbe Callback-Idee, lösen aber ein anderes Problem — dazu mehr in OPA vs Kyverno. Hier geht es um Ereignis-Webhooks zwischen Diensten.

Einen Webhook in vier Schritten einrichten

Egal bei welchem Anbieter — der erste empfangene Webhook folgt immer denselben vier Schritten:

  1. Einen Endpunkt anlegen, der POST-Anfragen annimmt und mit 2xx antwortet — zum Beispiel /webhooks/billing. Lokal machen Sie ihn über einen Tunnel erreichbar (siehe Testen weiter unten).
  2. Die URL beim Anbieter registrieren und nur die Ereignistypen wählen, die Sie brauchen. Stripe und GitHub raten beide davon ab, alles zu abonnieren: Jedes zusätzliche Ereignis ist Last auf Ihrem Endpunkt.
  3. Das Signatur-Secret speichern, das der Anbieter anzeigt — bei Stripe beginnt es mit whsec_ —, und zwar im Secret Manager, nicht im Code.
  4. Ein Testereignis senden und beide Seiten prüfen: GitHub sendet ein ping-Ereignis und kann frühere Zustellungen erneut senden, Stripe hat stripe trigger, und jeder Anbieter zeigt ein Zustell-Log mit dem Statuscode, den Ihr Endpunkt zurückgegeben hat.

Das reicht für eine Demo. Der Rest dieses Leitfadens handelt davon, was sie produktionstauglich macht.

Webhook vs. API-Polling vs. WebSockets

Webhooks gibt es, weil die Alternative — Polling — fast jede Anfrage verschwendet. Ändert sich eine Bestellung zweimal am Tag und Sie fragen jede Minute, liefern 1.438 Ihrer 1.440 täglichen Anfragen „nichts Neues“, und Sie erfahren jede Änderung trotzdem bis zu eine Minute zu spät.

Polling vs. Webhooks vs. WebSockets: Polling fragt immer wieder und hört meist nichts Neues, ein Webhook sendet einen POST pro Ereignis, ein WebSocket hält eine Verbindung in beide Richtungen offen.
Polling bezahlt jede Frage. Ein Webhook bezahlt nur die Antworten.
Webhook vs. API-Polling vs. WebSockets
API-Polling Webhook WebSocket / SSE
Wer startet die Anfrage Sie, nach Zeitplan Der Anbieter, pro Ereignis Client öffnet einmal, Server pusht
Latenz Bis zu einem Polling-Intervall Sekunden Millisekunden
Verschwendete Anfragen Die meisten Keine Keine, aber eine Verbindung pro Client
Was Sie betreiben müssen Einen Scheduler Einen öffentlichen HTTPS-Endpunkt Einen zustandsbehafteten Verbindungsserver
Verpasste Ereignisse Beim nächsten Poll nachgeholt Verloren, außer bei Retry oder Abgleich Verloren, solange getrennt
Typischer Einsatz Kleine Mengen, kein Webhook-Angebot Integration Backend zu Backend Live-Oberflächen: Chat, Dashboards, Präsenz

Ehrlich zusammengefasst: Webhooks sind der Standard für die Integration von Server zu Server, WebSockets oder Server-Sent Events schicken Updates an Clients, die gerade online sind, und Polling bleibt das richtige Werkzeug, wenn der Anbieter nichts anderes bietet — oder als Abgleichsjob, der auffängt, was Webhooks verpasst haben. Die meisten robusten Integrationen kombinieren Webhooks mit einem regelmäßigen Abgleich, und Shopifys eigene Dokumentation empfiehlt genau das.

Wie eine Webhook-Zustellung wirklich abläuft

Payloads: schlank oder vollständig

Anbieter entscheiden, was in den Body kommt:

  • Eine vollständige Payload (auch Snapshot) enthält das ganze Objekt, wie es zum Zeitpunkt des Ereignisses war.
  • Eine schlanke Payload („thin“) enthält nur Kennungen — {"id": "inv_123"} — und Sie holen den aktuellen Zustand über die API.

Schlanke Payloads sind kleiner, nie veraltet und machen den Datenzugriff nachvollziehbar, weil der Empfänger aktiv anfragen muss. Vollständige Payloads sparen einen API-Aufruf. Stripe bietet inzwischen beides an und empfiehlt schlanke Ereignisse für neue Integrationen; die Spezifikation Standard Webhooks beschreibt beide und empfiehlt kleine Payloads, meist unter 20 KB, mit Links statt eingebetteter großer Daten.

Statuscodes und Timeouts

Ein 2xx bedeutet zugestellt. Alles andere ist ein Fehler, und Standard Webhooks schlägt vor, wie Absender reagieren sollen:

  • 3xx — Fehler. Keinen Weiterleitungen folgen; der Empfänger soll die URL aktualisieren.
  • 410 Gone — der Endpunkt will diese Webhooks nicht mehr; deaktivieren.
  • 429, 502, 504 — der Empfänger ist überlastet; drosseln und, falls vorhanden, Retry-After beachten.

Bei Timeouts tun sich Empfänger am meisten weh, denn sie sind kurz und unterschiedlich:

Statuscodes und Timeouts
Anbieter Ihr Endpunkt muss antworten innerhalb von
Slack (Events API) 3 Sekunden
Shopify 5 Sekunden (Verbindungsaufbau in 1 Sekunde)
GitHub 10 Sekunden
Stripe „schnell“, vor jeder komplexen Logik
Standard Webhooks (Empfehlung für Absender) ein Timeout von 15–30 Sekunden

Mindestens einmal, nicht genau einmal

Alle Anbieter in diesem Artikel stellen mindestens einmal zu. Ein Retry passiert immer dann, wenn der Anbieter Ihr 2xx nicht sieht — auch dann, wenn Ihr Handler die Arbeit erledigt hat, die Antwort aber auf dem Rückweg verloren ging. Dasselbe Ereignis wird also gelegentlich zweimal ankommen. Stripe sagt außerdem klar, dass die Reihenfolge der Zustellung nicht garantiert ist. Deshalb dreht sich die Empfängerseite in diesem Artikel vor allem um Idempotenz — dieselbe Idee wie bei API-Retries mit Idempotency Keys, nur für eingehende Ereignisse.

So machen es Stripe, GitHub, Shopify und Slack

Es gibt kein einheitliches Webhook-Protokoll, deshalb sieht die Dokumentation jedes Anbieters etwas anders aus. So unterscheiden sich die vier am häufigsten integrierten Anbieter und die Spezifikation Standard Webhooks:

So machen es Stripe, GitHub, Shopify und Slack
Stripe GitHub Shopify Slack (Events API) Standard Webhooks
Signatur-Header Stripe-Signature: t=…,v1=… X-Hub-Signature-256: sha256=… X-Shopify-Hmac-SHA256 X-Slack-Signature: v0=… webhook-signature: v1,…
Algorithmus, Kodierung HMAC-SHA256, Hex HMAC-SHA256, Hex HMAC-SHA256, Base64 HMAC-SHA256, Hex HMAC-SHA256 Base64 oder Ed25519 (v1a)
Was signiert wird timestamp.body Body Body v0:timestamp:body id.timestamp.body
Schutz vor Replay Zeitstempel; Bibliotheken standardmäßig 5 Min. Nicht in der Signatur; X-GitHub-Delivery speichern Nicht in der Signatur; Webhook-ID speichern Zeitstempel; nach 5 Min. ablehnen Zeitstempel und Nachrichten-ID
Automatische Retries Bis zu 3 Tage, exponentieller Backoff (Live-Modus) Keine — manuell oder per REST-API neu zustellen 8-mal in 4 Stunden, dann wird das Abonnement entfernt* 3-mal: sofort, nach 1 Min., nach 5 Min. Beispiel-Zeitplan über rund 3 Tage
Schlüssel für Deduplizierung Ereignis-id X-GitHub-Delivery X-Shopify-Webhook-Id event_id webhook-id

* Shopify entfernt über die Admin-API angelegte Abonnements nach acht Fehlschlägen in Folge und benachrichtigt den Notfallkontakt der App per E-Mail. Slack deaktiviert Ereignis-Abonnements vorübergehend, wenn mehr als 95 % der Zustellungen innerhalb von 60 Minuten scheitern (Apps mit weniger als 1.000 Ereignissen pro Stunde sind ausgenommen), und bietet optional Delayed Events an: stündliche Retries über 24 Stunden.

Was das praktisch bedeutet:

  • GitHub rettet Sie nicht. Ist Ihr Endpunkt ausgefallen, bleiben diese Zustellungen fehlgeschlagen, bis Sie sie neu auslösen. GitHub empfiehlt selbst ein geplantes Skript, das fehlgeschlagene Zustellungen über die REST-API auflistet und erneut zustellt.
  • Den Zeitstempel zu signieren ist nicht selbstverständlich. GitHub und Shopify signieren nur den Body, ihre Signatur allein verhindert also kein Replay. Sie müssen die Zustell-ID speichern.
  • Die Kodierungen unterscheiden sich. Shopifys Signatur ist Base64, die von Stripe, GitHub und Slack Hex. Einen Base64-Digest mit einem Hex-Wert zu vergleichen, ist ein klassischer Nachmittag mit „die Signatur passt nie“.

Webhook-Sicherheit: Signaturen prüfen

Ihre Webhook-URL ist öffentlich. Jeder, der sie findet, kann darauf POSTen. Stripes Dokumentation benennt das Risiko klar: Ohne Prüfung kann ein Angreifer gefälschte Ereignisse senden und etwa Bestellungen ausliefern oder Zugänge freischalten lassen. Die Abwehr ist eine HMAC-Signatur mit gemeinsamem Secret — und sie wirkt nur, wenn jeder Schritt stimmt.

Schritte der Webhook-Signaturprüfung: Roh-Body lesen, signierten Inhalt aus ID, Zeitstempel und Body nachbauen, HMAC-SHA256 mit dem Secret berechnen, in konstanter Zeit vergleichen, Zeitfenster prüfen, dann nach ID deduplizieren.
Sechs Prüfungen in genau dieser Reihenfolge. Wer eine auslässt, öffnet eine Lücke, die die anderen nicht schließen.
  1. Roh-Body lesen. Prüfen Sie genau die Bytes, die der Anbieter gesendet hat, vor jedem JSON-Parsing.
  2. Signierten Inhalt nachbauen, so wie der Anbieter ihn definiert — nur der Body (GitHub, Shopify), timestamp.body (Stripe), v0:timestamp:body (Slack), id.timestamp.body (Standard Webhooks).
  3. HMAC-SHA256 berechnen mit dem Secret des Endpunkts.
  4. In konstanter Zeit vergleichen — hmac.compare_digest in Python, crypto.timingSafeEqual in Node.js. GitHubs Dokumentation sagt es direkt: niemals ein einfaches ==.
  5. Zeitstempel prüfen gegen Ihre Uhr. Fünf Minuten sind die übliche Toleranz; Stripe warnt, dass eine Toleranz von 0 die Prüfung komplett abschaltet, statt sie streng zu machen.
  6. Nach ID deduplizieren — dazu der nächste Abschnitt.

Der Raw-Body-Fehler

Der häufigste Grund für eine scheiternde Prüfung: Der Handler prüft einen Body, der bereits geparst und neu serialisiert wurde. Express mit express.json(), Flask mit request.json und die meisten API-Frameworks wandeln den Body in ein Objekt um, bevor Ihr Code läuft. Beim Zurückserialisieren ändern sich Leerzeichen oder die Reihenfolge der Schlüssel — und eine kryptografische Signatur ändert sich mit einem einzigen Byte. Stripe und Shopify nennen genau das in ihren Troubleshooting-Anleitungen; Stripes Lösung für Express ist, die Webhook-Route vor der JSON-Middleware zu registrieren.

Eine Prüffunktion, die man lesen kann

Hier eine vollständige Prüfung von Standard-Webhooks-Signaturen in Python — ohne Bibliothek, damit jeder Schritt sichtbar ist:

verify.py
import base64
import hashlib
import hmac
import time
TOLERANCE_SECONDS = 5 * 60
def verify(secret: str, headers, body: bytes) -> None:
"""Raise ValueError unless this is a fresh, correctly signed delivery."""
msg_id = headers["webhook-id"]
timestamp = headers["webhook-timestamp"]
key = base64.b64decode(secret.removeprefix("whsec_"))
signed_content = f"{msg_id}.{timestamp}.".encode() + body
expected = base64.b64encode(
hmac.new(key, signed_content, hashlib.sha256).digest()
).decode()
# Several signatures are sent while a secret is being rotated.
for signature in headers["webhook-signature"].split(" "):
version, _, value = signature.partition(",")
if version == "v1" and hmac.compare_digest(value, expected):
break
else:
raise ValueError("no matching signature")
if abs(time.time() - int(timestamp)) > TOLERANCE_SECONDS:
raise ValueError("timestamp outside tolerance")

Zwei Details übersieht man leicht. Der Signatur-Header ist eine Liste: Während einer Secret-Rotation signiert der Absender mit altem und neuem Schlüssel, die Prüfung gelingt also mit dem, den Sie gerade haben. Und das Secret ist nach dem Präfix whsec_ Base64-kodiert — Sie dekodieren es, um die Schlüssel-Bytes zu erhalten.

In der Produktion sollten Sie das offizielle SDK des Anbieters oder die Referenzbibliothek von Standard Webhooks nutzen — es gibt sie für Python, JavaScript/TypeScript, Java/Kotlin, Go, Rust, Ruby, PHP, C# und Elixir. Es einmal selbst zu schreiben, ist trotzdem der beste Weg zu verstehen, wovor die Bibliothek Sie schützt.

Mehr als die Signatur

  • IP-Allowlists. Stripe veröffentlicht seine Webhook-IP-Adressen, GitHub liefert seine Bereiche über den API-Endpunkt GET /meta. Nützlich als zusätzliche Schicht; die Adressen ändern sich, also automatisieren Sie die Aktualisierung.
  • Mutual TLS. Slack kann sich zusätzlich zur Signatur mit einem Client-Zertifikat ausweisen; Ihr TLS-Terminator prüft es.
  • Secret-Rotation. Bei Stripe bleibt das alte Secret bis zu 24 Stunden gültig, während Sie das neue ausrollen, und jede aktive Variante erhält eine eigene Signatur. Planen Sie die Rotation, bevor ein Leak sie erzwingt.
  • Die Route vom CSRF-Schutz ausnehmen, nicht von der Authentifizierung. Frameworks wie Rails und Django lehnen POSTs ohne CSRF-Token ab; die Signatur übernimmt hier die Authentifizierung.
  • Niemals Zugangsdaten in die Webhook-URL schreiben. Ein ?token= in der URL landet in Proxy- und Zugriffslogs. GitHub warnt ausdrücklich davor, API-Schlüssel oder andere Zugangsdaten in die URL zu schreiben, und verweist stattdessen auf das Webhook-Secret.

Webhooks zuverlässig empfangen: schnell bestätigen, später verarbeiten

Ein Handler, der die Anfrage prüft, drei Tabellen aktualisiert, zwei APIs aufruft und dann mit 200 antwortet, läuft in der Entwicklung problemlos. In der Produktion schiebt ihn die erste langsame Datenbankabfrage über Slacks 3 oder Shopifys 5 Sekunden, der Anbieter wiederholt — und nun laufen zwei Kopien desselben Ereignisses gleichzeitig.

Alle Anbieter empfehlen dasselbe Muster: prüfen, in die Queue legen, mit 2xx antworten und im Worker verarbeiten.

app.py
from fastapi import FastAPI, Request, Response
app = FastAPI()
@app.post("/webhooks/billing")
async def billing_webhook(request: Request) -> Response:
body = await request.body() # raw bytes, before any JSON parsing
try:
verify(WEBHOOK_SECRET, request.headers, body)
except (KeyError, ValueError):
return Response(status_code=401)
await queue.publish(body, webhook_id=request.headers["webhook-id"])
return Response(status_code=204)

Der Handler tut jetzt fast nichts mehr, was langsam sein kann, und antwortet auch bei Lastspitzen in Millisekunden — und die Spitzen sind real: Stripe weist darauf hin, dass der Monatsanfang, an dem alle Abonnements verlängert werden, synchron arbeitende Endpunkte überlasten kann.

Den Worker idempotent machen

Der Worker muss davon ausgehen, dass jedes Ereignis zweimal ankommt. Speichern Sie verarbeitete IDs mit einer Eindeutigkeitsbedingung — und zwar in derselben Transaktion wie die eigentliche Änderung:

INSERT INTO processed_webhooks (webhook_id, processed_at)
VALUES ($1, now())
ON CONFLICT (webhook_id) DO NOTHING
RETURNING webhook_id;
-- No row returned: a duplicate. Commit nothing else and acknowledge.

Werden Insert und fachliche Änderung gemeinsam committet, kann ein Absturz dazwischen weder „verarbeitet, aber nicht vermerkt“ noch das Umgekehrte hinterlassen. Nutzen Sie die stabile ID des Anbieters — Stripes Ereignis-id, X-GitHub-Delivery, X-Shopify-Webhook-Id, Slacks event_id oder webhook-id. Ein Detail: Shopify sendet pro Abonnement eine eigene Zustellung, jede mit eigener X-Shopify-Webhook-Id, aber derselben X-Shopify-Event-Id. Und Stripe weist darauf hin, dass für dieselbe Änderung in manchen Fällen zwei getrennte Event-Objekte entstehen, die Sie an Objekt-ID plus Ereignistyp erkennen.

Ein Webhook-Handler ist korrekt, wenn dasselbe Ereignis — doppelt, verspätet oder in falscher Reihenfolge empfangen — zum selben Ergebnis führt wie einmal und pünktlich.

Der Reihenfolge nicht trauen

Weil Ereignisse in falscher Reihenfolge ankommen können — invoice.paid vor invoice.created —, darf ein älteres Ereignis keinen neueren Zustand überschreiben. Vergleichen Sie vor dem Schreiben eine Version oder ein updated-Feld, oder behandeln Sie den Webhook als Signal und holen Sie das aktuelle Objekt über die API. Stripe empfiehlt den zweiten Weg und rät davon ab, den Zeitstempel created zur Sortierung zu nutzen, weil verschiedene Ereignisse dieselbe Sekunde haben können.

Für den Ausfall planen, der kommen wird

Ihr Endpunkt wird irgendwann ausfallen. Was dann passiert, hängt vom Anbieter ab: Stripe wiederholt drei Tage lang, Shopify gibt nach vier Stunden auf, und GitHub hat es nie versucht. Bauen Sie deshalb zwei Dinge, bevor Sie sie brauchen:

  • Einen Abgleichsjob, der regelmäßig aktuelle Änderungen über die API des Anbieters holt und in denselben idempotenten Worker gibt.
  • Ein Wiederherstellungs-Runbook: Stripes List-Events-API liefert Ereignisse mit fehlgeschlagener Zustellung (delivery_success=false) der letzten 30 Tage; GitHubs REST-API listet fehlgeschlagene Zustellungen auf und stellt sie neu zu.

Die Kennzahlen, die zuerst warnen

Die meisten Webhook-Vorfälle sind in den Metriken sichtbar, bevor jemand ein Ticket schreibt:

  • Fehlerquote Ihrer Antworten. Shopify selbst wertet eine Fehlerquote über 0,5 % als überdurchschnittlich.
  • Antwortzeit als hohes Perzentil, nicht als Durchschnitt. Shopify zeigt das 90. Perzentil und markiert Antworten zwischen vier und fünf Sekunden — eine langsame Abfrage vom Timeout entfernt.
  • Fehlgeschlagene Signaturprüfungen. Ein plötzlicher Anstieg bedeutet entweder einen Angriff oder ein Secret, das nur auf einer Seite rotiert wurde. Beides ist einen Alarm wert.
  • Queue-Länge und Anzahl in der Dead-Letter-Queue auf der Worker-Seite. Ein wachsender Rückstau ist ein Ausfall, der nur noch nicht passiert ist.

Webhooks versenden: Was Anbieter ihren Empfängern schulden

Wenn Ihr Produkt Webhooks versendet, sitzen Sie auf der schwierigen Seite. Der Empfänger braucht ein paar gute Gewohnheiten, der Absender braucht Infrastruktur.

Webhook-Zustellungspipeline: Das Ereignis landet in derselben Transaktion in einer Outbox, ein Dispatcher signiert es und sendet über einen Egress-Proxy, Fehlschläge werden mit exponentiellem Backoff und Jitter wiederholt, ausgeschöpfte Nachrichten gehen in eine Dead-Letter-Queue, während der Empfänger prüft, bestätigt und in die Queue legt.
Zuverlässige Zustellung ist auf beiden Seiten eine Pipeline: Outbox und Retries beim Absender, Queue und Deduplizierung beim Empfänger.

1. Kein Ereignis verlieren: Transactional Outbox

Wenn Sie die Bestellung committen und danach den Webhook-Dispatcher aufrufen, verliert ein Absturz dazwischen das Ereignis. Schreiben Sie das Ereignis in derselben Transaktion wie die fachliche Änderung in eine Outbox-Tabelle und lassen Sie einen eigenen Prozess veröffentlichen. Es ist dasselbe Muster, mit dem sich eine Million Benachrichtigungen versenden lässt, und es macht aus „vielleicht gesendet“ ein „steht in der Tabelle, bis es zugestellt ist“.

2. Retries mit exponentiellem Backoff und Jitter

Fehlschläge sind normal. Standard Webhooks empfiehlt einen Zeitplan über mehrere Tage mit exponentiellem Backoff und zufälligem Jitter, damit nicht Tausende Retries im selben Moment auf einen sich erholenden Endpunkt treffen. Der Beispielplan der Spezifikation: sofort, dann nach 5 Sekunden, 5 Minuten, 30 Minuten, 2 Stunden, 5 Stunden, 10 Stunden, 14 Stunden, 20 Stunden und 24 Stunden. Die klassische Umsetzung ist „Full Jitter“, beschrieben von Marc Brooker im AWS Architecture Blog:

delay = min(max_delay, base_delay * 2**attempt)
sleep_for = random.uniform(0, delay)

Erzeugen Sie für jeden Versuch einen neuen Zeitstempel und eine neue Signatur — Stripe macht es so —, damit ein legitimer Retry nie am Replay-Fenster des Empfängers scheitert.

3. Dead-Letter-Queue, deaktivieren, einen Menschen informieren

Nach dem letzten Retry kommt die Nachricht in eine Dead-Letter-Queue, statt verworfen zu werden, und der Kunde kann sie erneut auslösen. Scheitert ein Endpunkt lange Zeit, deaktivieren Sie ihn und informieren den Verantwortlichen über einen anderen Kanal — Shopify schreibt dem Notfallkontakt der App, Slack dem App-Inhaber. Behandeln Sie 410 Gone als ausdrückliche Bitte aufzuhören.

4. Die eigene Plattform vor Kunden-Endpunkten schützen

Jeder Endpunkt, den Sie aufrufen, ist eine URL, die ein Kunde eingetippt hat. Damit wird Ihr Dispatcher zum perfekten Werkzeug für Server-Side Request Forgery (SSRF), wenn Sie ihn nicht abschotten — ein Angreifer registriert http://169.254.169.254/ oder die Adresse eines internen Dienstes, und Ihr System ruft sie für ihn auf. Standard Webhooks empfiehlt zwei Maßnahmen zusammen: allen Webhook-Verkehr über einen Egress-Proxy leiten, der interne IP-Adressen filtert (Stripes Open-Source-Tool Smokescreen ist einer), und den Dispatcher in einem Subnetz betreiben, das interne Dienste nicht erreicht. Lösen Sie DNS beim Verbindungsaufbau auf und prüfen Sie die IP dann, nicht nur beim Speichern der URL — sonst umgeht ein später geänderter DNS-Eintrag die Prüfung.

Begrenzen Sie außerdem jeden Endpunkt: ein Timeout (die Spezifikation empfiehlt 15–30 Sekunden), eine Obergrenze für parallele Zustellungen pro Endpunkt und Backoff bei 429 und 5xx — dieselben Prinzipien wie beim Rate Limiting Ihrer eigenen API, nur nach außen gerichtet. Ein langsamer Kunden-Endpunkt darf die Zustellung an alle anderen nicht blockieren.

5. Empfängern geben, was sie zum Vertrauen brauchen

  • Ein Secret pro Endpunkt, nie kundenübergreifend, mit Rotation ohne Ausfallzeit durch zeitweises Signieren mit beiden Schlüsseln.
  • Filter nach Ereignistyp, damit Empfänger nur bekommen, was sie abonniert haben.
  • Statische Quell-IPs, falls Ihre Kunden hinter Unternehmens-Firewalls sitzen.
  • Zustell-Logs und manuelles Replay — die Spezifikation nennt Einblick in Fehlschläge und Replay „immens wichtig“.
  • Versionierte Payloads: Ein Ereignistyp sollte immer dasselbe Schema haben.

Standard Webhooks: ein Format statt fünfzig

Jede Zeile der Anbieter-Tabelle oben ist ein wenig anders, und jeder Empfänger muss die Prüfung für jeden Anbieter neu lernen. Standard Webhooks ist der Versuch der Branche, das zu beheben: eine offene Spezifikation (Version 1.0.0, Apache 2.0), geschrieben von einem Lenkungsausschuss mit Engineers von Svix, Zapier, Twilio, Kong, ngrok, Supabase, Mux und Lob.

Sie standardisiert, was dieser Artikel beschreibt:

  • Drei Header: webhook-id, webhook-timestamp (Unix-Sekunden) und webhook-signature.
  • Eine Signatur über id.timestamp.body — HMAC-SHA256 (v1) mit einem Secret mit Präfix whsec_ oder Ed25519 (v1a) für asymmetrisches Signieren, bei dem Empfänger mit einem öffentlichen Schlüssel prüfen und der Signierschlüssel den Absender nie verlässt. Die Spezifikation bevorzugt asymmetrische Signaturen, wo Sie sich die Rechenzeit leisten können.
  • Eine Payload-Form mit type, timestamp und data.
  • Betriebsregeln: Retries mit Backoff und Jitter, Umgang mit Statuscodes, ein Timeout von 15–30 Sekunden, HTTPS und SSRF-Schutz.

OpenAI, Anthropic, Google Gemini, Twilio, Kong, PagerDuty, Clerk und Svix gehören zu den als kompatibel gelisteten Implementierungen. Wer ein neues Webhook-System entwirft, hat kaum einen Grund, darauf zu verzichten — es lässt sich neben einem bestehenden Schema einführen, indem Sie die neuen Header zusätzlich zu den alten senden, und Ihre Empfänger bekommen Referenzbibliotheken in neun Sprachen geschenkt.

Und CloudEvents?

CloudEvents wird oft im selben Atemzug genannt, beantwortet aber eine andere Frage. Es ist eine CNCF-Spezifikation — seit Januar 2024 im Status „Graduated“ —, die beschreibt, wie ein Ereignis in einem gemeinsamen Format aussieht, mit Bindings für HTTP, Kafka, AMQP, MQTT und NATS und einer eigenen Web-Hook-Spezifikation. Eventarc auf Google Cloud stellt Ereignisse im CloudEvents-Format zu. Standard Webhooks dagegen regelt die sichere Zustellung über HTTP: Signaturen, Replay-Schutz, Retries und SSRF. Nutzen Sie CloudEvents, wenn dieselben Ereignisse über mehrere Transporte und Plattformen laufen, und Standard Webhooks, wenn Sie HTTP-Callbacks an Endpunkte anderer Unternehmen senden. Das eine schließt das andere nicht aus.

Webhooks im großen Maßstab auf Google Cloud

Die Muster oben lassen sich direkt auf Managed Services abbilden. Auf Google Cloud würde ich beide Richtungen so aufbauen:

Empfangen:

  • Ein Cloud-Run-Dienst prüft die Signatur, veröffentlicht das rohe Ereignis in Pub/Sub und antwortet mit 2xx. Cloud Run skaliert mit der Spitze, Pub/Sub puffert sie.
  • Worker an einem Pub/Sub-Abonnement mit Dead-Letter-Topic (einstellbar nach 5 bis 100 Zustellversuchen) und die Deduplizierungstabelle in Cloud SQL, AlloyDB oder Spanner.
  • Signatur-Secrets im Secret Manager und Cloud Armor vor dem Endpunkt, wenn der Anbieter seine Quell-IPs veröffentlicht.
  • Manche Anbieter umgehen HTTP ganz: Shopify kann Webhooks direkt in ein Pub/Sub-Topic zustellen, und für Ereignisse von Google Cloud selbst leitet Eventarc sie ganz ohne Webhook-Endpunkt weiter.

Versenden:

  • Die Outbox in Ihrer Datenbank, weitergereicht an Pub/Sub.
  • Cloud Tasks für die eigentlichen HTTP-Aufrufe: Jede Queue hat eine eigene Zustellrate, ein Parallelitätslimit und eine Retry-Richtlinie mit exponentiellem Backoff — das ist der größte Teil eines Webhook-Dispatchers ab Werk.
  • Der Dispatcher in einer eigenen VPC mit Cloud NAT auf reservierten statischen IPs — so bekommen Empfänger feste Quelladressen für ihre Allowlist — und Egress-Firewallregeln, die private Bereiche und den Metadatenserver sperren.

Webhooks lokal testen

Anbieter erreichen kein localhost, deshalb ist die Entwicklung mit Webhooks umständlich. Die üblichen Werkzeuge, vom schnellsten zum realistischsten:

  • webhook.site — eine Wegwerf-URL, die jede eingehende Anfrage anzeigt. Ideal, um zu sehen, was ein Anbieter genau sendet, bevor Sie Code schreiben.
  • Ein Tunnel — ngrok oder cloudflared gibt Ihrem lokalen Port eine öffentliche HTTPS-URL. Stripes Dokumentation schlägt genau dafür ngrok vor.
  • Anbieter-CLIs — stripe listen --forward-to localhost:4242/webhook leitet echte Testereignisse weiter und zeigt ein Signatur-Secret an, und stripe trigger payment_intent.succeeded löst eines auf Abruf aus.
  • smee.io — das Relay, das GitHubs eigene Testanleitung nutzt, um Webhook-Zustellungen an einen lokalen Server weiterzuleiten.
  • Werkzeuge von Standard Webhooks — das Projekt bietet Web-Tools zum Signieren, Prüfen und Simulieren von Nachrichten.

Machen Sie daraus Tests: Speichern Sie echte Payloads als Fixtures und spielen Sie sie gegen Ihren Handler ab — mit falscher Signatur, abgelaufenem Zeitstempel, doppelter Zustellung und zwei Ereignissen in umgekehrter Reihenfolge. Bestehen diese vier Tests, übersteht Ihr Handler das meiste, was die Produktion mit ihm anstellt.

Selbst bauen oder kaufen?

Sie müssen das nicht alles selbst bauen. Managed- und Open-Source-Angebote decken beide Richtungen ab:

  • Svix — Webhook-Versand als Dienst, mit einem MIT-lizenzierten Server zum Selbstbetrieb, der Zustellungen an interne IP-Adressen standardmäßig blockiert; sein CEO sitzt im Lenkungsausschuss von Standard Webhooks.
  • Hookdeck — ein Gateway für eingehende Webhooks, das Zustellungen vor Ihrem Endpunkt puffert und wiederholt; GitHubs Dokumentation nennt es als eine Möglichkeit, rechtzeitig zu antworten.
  • Convoy — ein Webhook-Gateway für Versand und Empfang, mit Retries, Rate Limiting, statischen IPs und Circuit Breaking. Der Quellcode ist öffentlich, steht aber unter der Elastic License 2.0 — das ist Source-available, keine OSI-Open-Source-Lizenz. Prüfen Sie die Bedingungen, bevor Sie ein Produkt darauf bauen.

Bauen Sie selbst, wenn Webhooks nur ein paar Ereignistypen auf Infrastruktur sind, die Sie ohnehin betreiben — Pub/Sub plus Cloud Tasks bringt Sie weit. Kaufen Sie, wenn Webhooks für Ihre Kunden ein Produktmerkmal sind: Oberflächen zur Endpunktverwaltung, Logs pro Kunde, Replay und Auswertungen sind viel Arbeit außerhalb Ihres Kerngeschäfts.

Checkliste für die Produktion

Webhooks empfangen:

  • HTTPS-Endpunkt, CSRF-Ausnahme nur für diese Route
  • Signatur über den Roh-Body geprüft, Vergleich in konstanter Zeit, richtige Kodierung
  • Zeitstempel-Toleranz von wenigen Minuten, Serveruhr per NTP synchronisiert
  • Prüfen, in die Queue legen, 2xx zurückgeben — deutlich innerhalb des Anbieter-Timeouts
  • Idempotenter Worker: Eindeutigkeitsbedingung auf der Ereignis-ID, in derselben Transaktion wie die Änderung
  • Robust gegen falsche Reihenfolge: Versionsprüfung oder aktuellen Zustand holen
  • Abgleichsjob und dokumentiertes Wiederherstellungs-Runbook
  • Secrets im Secret Manager, Rotation getestet

Webhooks versenden:

  • Transactional Outbox, damit kein Ereignis verloren geht
  • Retries mit exponentiellem Backoff und Jitter über Tage, neue Signatur pro Versuch
  • Dead-Letter-Queue, automatisches Deaktivieren von Endpunkten und Benachrichtigung; 410 stoppt die Zustellung
  • SSRF-Schutz: Egress-Proxy, isoliertes Netz, IP-Prüfung beim Verbindungsaufbau
  • Timeout, Parallelitätslimit und Backoff bei 429/5xx pro Endpunkt
  • Ein Secret pro Endpunkt, Rotation ohne Ausfallzeit, statische Quell-IPs bei Bedarf
  • Zustell-Logs, manuelles Replay, Filter nach Ereignistyp, versionierte Schemas
  • Header und Signaturverfahren von Standard Webhooks

Fazit

Ein Webhook ist der einfachste Integrationsbaustein überhaupt — ein HTTP-POST pro Ereignis —, und genau diese Einfachheit verdeckt seine Fehlerquellen. Die Anfrage stammt vielleicht nicht von dem, der sie zu senden behauptet, kommt vielleicht doppelt, verspätet oder vor dem Ereignis, von dem sie abhängt.

Jede Regel in diesem Leitfaden folgt aus einer Idee: Die Zustellung erfolgt mindestens einmal, und das Netz ist feindlich — also muss der Empfänger alles prüfen und Wiederholungen verkraften, und der Absender darf nichts vergessen und keiner URL blind vertrauen. Wenn beide Hälften stimmen, sind Webhooks nicht mehr der wackligste Teil Ihrer Integration, sondern der langweiligste — und genau das wollen Sie.

Retries auf der anderen Seite eines Webhooks sind dasselbe Problem wie Retries an Ihrer eigenen API — Idempotency Keys erklären die Speicherseite im Detail.

Häufig gestellte Fragen

Was ist ein Webhook einfach erklärt?

Mit einem Webhook benachrichtigt ein Dienst einen anderen, dass etwas passiert ist. Sie geben dem Anbieter eine URL, und sobald ein Ereignis eintritt — eine Zahlung geht ein, ein Pull Request wird eröffnet, eine Bestellung wird angelegt —, sendet der Anbieter einen HTTP-POST mit einer JSON-Beschreibung des Ereignisses an diese URL. Man spricht oft von einer umgekehrten API: Bei einer normalen API rufen Sie den Dienst auf, beim Webhook ruft der Dienst Sie auf.

Was ist eine Webhook-URL?

Eine Webhook-URL ist die Adresse des Endpunkts in Ihrer Anwendung, der die POST-Anfragen des Anbieters empfängt, zum Beispiel https://api.example.com/webhooks/billing. Sie registrieren sie beim Anbieter zusammen mit den gewünschten Ereignistypen. In der Produktion muss es eine öffentliche HTTPS-URL sein; für die lokale Entwicklung liefert ein Tunnel wie ngrok oder ein Relay wie smee.io eine temporäre. Schreiben Sie niemals API-Schlüssel oder Tokens in die URL selbst — authentifizieren Sie Anfragen stattdessen über das Signatur-Secret des Anbieters.

Was ist der Unterschied zwischen Webhook und API?

Bei einer API sendet Ihr Code eine Anfrage und erhält eine Antwort — Sie entscheiden, wann Sie fragen. Bei einem Webhook sendet der Anbieter eine Anfrage an Ihren Endpunkt, sobald ein Ereignis eintritt — der Anbieter entscheidet, wann er Bescheid gibt. Webhooks sind meist Teil einer API: Sie empfangen den Webhook und rufen dann die API auf, um weitere Daten zu holen oder zu reagieren. Webhooks ersetzen das Polling, nicht die API.

Was ist der Unterschied zwischen Webhook und WebSocket?

Ein Webhook ist eine einzelne HTTP-Anfrage von Server zu Server pro Ereignis; zwischen den Ereignissen bleibt nichts verbunden, und der Empfänger muss einen öffentlichen HTTPS-Endpunkt betreiben. Ein WebSocket ist eine dauerhafte Verbindung in beide Richtungen, meist zwischen Browser oder App und Server, für Datenströme mit geringer Latenz wie Chat oder Live-Dashboards. Webhooks verbinden Backend-Systeme, WebSockets oder Server-Sent Events schicken Updates an Clients, die gerade online sind.

Wie sichere ich einen Webhook-Endpunkt ab?

Nutzen Sie HTTPS, prüfen Sie bei jeder Anfrage die Signatur mit dem gemeinsamen Secret (HMAC-SHA256 über den unveränderten Body, Vergleich in konstanter Zeit), lehnen Sie Anfragen mit einem Zeitstempel ab, der älter als einige Minuten ist, deduplizieren Sie nach Ereignis- oder Zustell-ID und bewahren Sie das Secret in einem Secret Manager mit Rotationsplan auf. IP-Allowlists und Mutual TLS sind sinnvolle Ergänzungen, wo der Anbieter sie unterstützt, ersetzen aber die Signaturprüfung nicht.

Warum schlägt die Prüfung der Webhook-Signatur fehl?

Fast immer, weil der geprüfte Body nicht genau die Bytes enthält, die der Anbieter gesendet hat. Frameworks parsen JSON, bevor Ihr Handler läuft, und eine erneute Serialisierung ändert Leerzeichen oder die Reihenfolge der Schlüssel — und damit den HMAC. Lesen Sie zuerst den Roh-Body, prüfen Sie ihn und parsen Sie danach. Weitere häufige Ursachen: das falsche Secret (Test statt Live oder CLI- statt Dashboard-Secret), die falsche Kodierung (Hex statt Base64) und eine Serveruhr, die außerhalb der Zeitstempel-Toleranz liegt.

Wie funktionieren Webhook-Retries?

Antwortet Ihr Endpunkt nicht innerhalb des Timeouts des Anbieters mit einem 2xx-Status, gilt die Zustellung als fehlgeschlagen, und die meisten Anbieter wiederholen sie mit wachsenden Abständen. Stripe wiederholt Live-Ereignisse bis zu drei Tage lang mit exponentiellem Backoff, Shopify achtmal innerhalb von vier Stunden, bevor das Abonnement entfernt wird, und Slack dreimal — fast sofort, nach einer Minute und nach fünf Minuten. GitHub wiederholt nicht automatisch; Sie stellen über die Oberfläche oder die REST-API erneut zu. Wegen der Retries kann dasselbe Ereignis mehrfach ankommen.

Wie teste ich Webhooks lokal?

Lassen Sie den Anbieter an eine öffentliche URL senden, die an Ihren Rechner weiterleitet. webhook.site zeigt ohne eine Zeile Code genau, was ein Anbieter schickt. Für echte Handler nutzen Sie einen Tunnel wie ngrok oder cloudflared, eine Anbieter-CLI wie stripe listen --forward-to localhost:4242/webhook oder smee.io für GitHub. Speichern Sie echte Payloads als Fixtures und spielen Sie sie in Tests ab — auch mit absichtlich falscher Signatur und doppelter Zustellung.

Was ist Standard Webhooks?

Standard Webhooks ist eine offene Spezifikation für das Versenden von Webhooks, betreut von einem Lenkungsausschuss mit Engineers von Svix, Zapier, Twilio, Kong, ngrok, Supabase, Mux und Lob. Sie definiert drei Header — webhook-id, webhook-timestamp und webhook-signature —, eine Signatur über id.timestamp.body mit HMAC-SHA256 oder Ed25519, einen Retry-Zeitplan sowie Vorgaben zu SSRF, Timeouts und Payload-Größe. OpenAI, Anthropic, Google Gemini, Twilio und Kong werden als kompatible Implementierungen genannt, und Referenzbibliotheken gibt es für neun Sprachen.

Aus der Community

Diskussion auf Mastodon & Bluesky

Antworten auf diesen Artikel aus dem offenen Netz, live geladen. Keine Werbung, keine Tracking-Skripte.

Antworten werden geladen …
Auf Mastodon antwortenAuf Bluesky antworten

Antworte dort — es erscheint hier automatisch.

ENDE