---
title: "Webhooks kommen doppelt. Rechnen Sie damit."
description: "Payment- und Plattform-Webhooks kommen im Retry-Fenster mindestens einmal an, nicht genau einmal. Welche Probleme doppelte Zustellung verursacht und wie Idempotenz sie abfängt."
author: "Luboš Zápotočný"
published: "2026-06-17"
language: "de"
canonical: "https://zapolu.com/de/blog/webhooks-kommen-doppelt/"
---

# Webhooks kommen doppelt. Rechnen Sie damit.

Lesen Sie die Webhook-Dokumentation eines beliebigen ernsthaften
Anbieters (Stripe, Shopify, Adyen, GoPay), und Sie finden in der
einen oder anderen Form dieselbe Warnung: Zustellungen können
mehrfach erfolgen. Innerhalb des Retry-Fensters lautet der Vertrag
*at-least-once*, nicht *exactly-once*; bleibt Ihr Endpoint das ganze
Fenster über nicht erreichbar, kommt das Event null Mal an, weshalb
ein Abgleich-Job, der den Zustand über die API des Anbieters
nachlädt, die Absicherung unter allem anderen bildet.
Exactly-once-Zustellung über ein unzuverlässiges Netz ist
bekanntermaßen unmöglich, also tun die Anbieter das Vernünftige:
Sind sie nicht sicher, dass Sie ein Event erhalten haben, schicken
sie es noch einmal.

Die meisten Integrationen sind geschrieben, als gäbe es diese
Klausel nicht. Sie funktionieren in der Demo und im ersten Monat;
dann passiert irgendwo zwischen dem Anbieter und der 200er-Antwort
ein Timeout, und dasselbe `order.paid`-Event kommt zweimal an.

## Was doppelte Zustellung konkret verursacht

- Der Kunde bekommt zwei Bestellbestätigungen.
- Der Bestand wird zweimal abgebucht; so entsteht das
  Phantom-„ausverkauft“.
- Eine Rechnung wird zweimal gestellt, was spätestens der
  Buchhaltung auffällt.
- Ein Treueguthaben wird zweimal gutgeschrieben, was niemandem
  auffällt, bis die Zahlen nicht mehr aufgehen.

Nichts davon ist ein Anbieter-Bug. Der Anbieter hat getan, was er
dokumentiert hat. Der Handler hat sich auf ein Versprechen
verlassen, das niemand gegeben hat.

## Idempotenz ist die eigentliche Lösung

Ein idempotenter Handler erzeugt denselben Endzustand, egal ob ein
Event einmal verarbeitet wird oder fünfmal. Diese eine Eigenschaft
absorbiert Retries, manuelle Replays und eine ganze Klasse von Race
Conditions gleich mit. Sie zu bekommen ist vor allem Disziplin:

1. **Geben Sie jedem Event einen Schlüssel.** Anbieter schicken
   eine Event-ID; tut Ihrer das nicht, leiten Sie eine aus stabilen
   Feldern ab. Speichern Sie verarbeitete IDs und überspringen Sie
   Duplikate. Setzen Sie das mit einem Unique Constraint in der
   Datenbank durch statt im Anwendungscode: Die Prüfung „habe ich
   das schon gesehen?“ hat dort ein Race-Fenster, der Constraint
   nicht.
2. **Ziehen Sie absoluten Zustand relativen Änderungen vor.**
   „Status auf bezahlt setzen“ ist von Natur aus idempotent.
   „Bestand um 2 verringern“ ist es nicht. Schickt der Anbieter
   Deltas, übersetzen Sie sie in Upserts gegen Ihre eigene
   Aufzeichnung des Events statt in blinde Arithmetik.
3. **Rechnen Sie auch mit falscher Reihenfolge.** Retries kommen
   außerdem nicht unbedingt in der Reihenfolge an, in der die Events
   entstanden sind: `order.updated` kann vor `order.created` landen.
   Versionsnummern oder Timestamps am Zieldatensatz („nur anwenden,
   wenn neuer“) fangen ab, was Annahmen über die Reihenfolge nicht
   können.

## Der Rest der Checkliste

Idempotenz ist der Kern; drei ergänzende Praktiken unterscheiden
Integrationen, die im Produktivbetrieb stabil laufen, von solchen,
die Support-Tickets erzeugen:

- **Schnell bestätigen, asynchron verarbeiten.** Signatur prüfen,
  Event persistieren, 200 zurückgeben und die eigentliche Arbeit
  aus einer Queue heraus erledigen. Langsame synchrone Handler
  laufen in Timeouts, und jeder Timeout löst genau die Retries aus,
  die Sie vermeiden wollten.
- **Authentifizieren Sie jeden Webhook.** Ein Webhook-Endpoint ist
  eine unauthentifizierte URL im öffentlichen Internet, die Ihren
  Geschäftszustand verändert. Die meisten Anbieter signieren die
  Payload mit einem HMAC, den Sie nachrechnen und vergleichen; manche
  (GoPay) geben Ihnen nur eine ID und erwarten, dass Sie den
  maßgeblichen Zustand über ihre API neu abrufen. So oder so: Handeln
  Sie nie auf Basis des Requests, wie er hereinkommt.
- **Halten Sie eine Dead-Letter-Queue mit Replay-Werkzeug.** Manche
  Events scheitern aus Gründen, die kein Retry behebt: eine
  gelöschte SKU, eine Lücke im Mapping. Parken Sie sie, alarmieren
  Sie darauf, und machen Sie das Replay eines einzelnen Events zu
  einem Button statt zu einer SSH-Session. Fällt die
  Drittanbieter-API ein paar Stunden aus, erspart Ihnen das
  Replay-Werkzeug den Incident.

Der Test für all das ist erfreulich einfach: Nehmen Sie die Events
von gestern aus dem Staging und stellen Sie jedes davon zweimal zu,
in gemischter Reihenfolge. Stimmt der Endzustand mit der Produktion
überein, ist Ihre Integration gegen doppelte Zustellung tatsächlich
robust.

Das ist der Standard, nach dem wir Integrationen bauen. Es ist der
Großteil dessen, was „[gebaut, um weiterzulaufen, wenn die API des
Drittanbieters gerade klemmt](/de/services/integrations/)“ auf
unserer Services-Seite bedeutet, und dasselbe Denken steckt in
unserer
[individuellen Backend-Entwicklung](/de/services/custom-development/).
Und wenn Ihre Bestandszahlen bereits driften, gehört die
Webhook-Schicht zu den
[üblichen Verdächtigen](/de/blog/warum-erp-und-shop-beim-bestand-auseinanderlaufen/).