Entwicklung & Technik9 Min. Lesezeit18. Juni 2026
Engineering-Handwerk · Tag 5: Idempotenz & Fehlertoleranz
Warum Retries und Webhooks im Betrieb doppelt zuschlagen und wie Idempotenz-Keys, Deduplizierung und Fehlertoleranz das sicher verhindern.
Willkommen zu Tag 5 unserer Serie Engineering-Handwerk. Heute geht es um ein Wort, das im Alltag selten fällt, aber über Vertrauen oder Ärger in deiner Software entscheidet: Idempotenz. Klingt akademisch, ist es aber nicht. Es ist der Unterschied zwischen „Der Kunde hat einmal bezahlt“ und „Der Kunde wurde dreimal belastet, ruft wütend an und will sein Geld zurück“. Wenn dein System im Betrieb läuft, unter echten Netzwerkbedingungen, mit echten Nutzern und echten Zahlungen, dann ist Idempotenz kein nettes Extra. Sie ist die Grundlage dafür, dass Fehler nicht zu Schäden werden.
In den ersten vier Tagen dieser Serie haben wir über sauberen Code, Architektur, klare Schnittstellen und Observability gesprochen. All das hilft dir, korrekte Software zu schreiben. Aber korrekte Software allein reicht nicht, sobald sie mit der unzuverlässigen echten Welt in Kontakt kommt. Netzwerke brechen ab. Antworten kommen verspätet oder gar nicht. Dienste antworten zweimal. Genau dort setzt das Thema an: Wie sorgst du dafür, dass eine Aktion, die versehentlich mehrfach ausgelöst wird, trotzdem nur einmal wirkt?
Warum „doppelt“ im Betrieb richtig teuer wird
In der Entwicklung sieht die Welt geordnet aus. Du klickst einen Button, der Request geht raus, die Antwort kommt zurück, alles gut. Im Betrieb ist das die Ausnahme, nicht die Regel. Zwischen deinem Server und dem Zahlungsdienstleister, zwischen deiner API und dem Client, zwischen zwei internen Services liegen Router, Load Balancer, Timeouts und Wartezeiten. Und überall lauert dieselbe Frage: Was passiert, wenn die Antwort nicht ankommt?
Die ehrliche Antwort lautet: Fast jedes seriöse System wiederholt dann den Versuch. Das ist kein Bug, das ist eine bewusste Entscheidung. Ein Client, der bei einem Timeout einfach aufgibt, ist fragil. Ein Client, der es noch einmal versucht, ist robust. Aber genau diese Robustheit erzeugt das Problem, um das es heute geht: Wiederholungen, sogenannte Retries, können dieselbe Aktion mehrfach beim Empfänger ankommen lassen.
Die drei Klassiker: Retries, Webhooks, Netzwerkfehler
Es gibt drei Situationen, in denen Doppelungen fast garantiert irgendwann auftreten. Die erste sind Retries. Der Client schickt einen Request, bekommt keine Antwort innerhalb des Timeouts und schickt ihn erneut. Der erste Request war aber vielleicht längst angekommen und wurde verarbeitet, nur die Antwort ging auf dem Rückweg verloren. Jetzt hast du zwei identische Vorgänge.
Die zweite sind Webhooks. Zahlungsdienstleister wie Stripe, PayPal oder Versanddienste rufen deine Endpunkte auf, um dich über Ereignisse zu informieren. Diese Anbieter garantieren nicht, dass ein Ereignis genau einmal ankommt. Sie garantieren im Gegenteil ausdrücklich, dass es mindestens einmal ankommt, und das heißt: manchmal zweimal, manchmal dreimal. Wenn dein Webhook-Handler bei jedem Aufruf eine Bestellung anlegt, hast du am Ende drei Bestellungen für eine Zahlung.
Die dritte sind schlicht Netzwerkfehler und Grauzonen. Ein Request wird abgebrochen, während er in der Mitte der Verarbeitung steckt. Der Nutzer sieht einen Fehler, drückt genervt noch einmal auf „Bezahlen“. Die erste Zahlung war aber bereits durch. Kein System hat hier etwas falsch gemacht, und trotzdem ist der Schaden da.
Was Idempotenz konkret bedeutet
Idempotenz ist ein Begriff aus der Mathematik, aber die Idee ist einfach. Eine Operation ist idempotent, wenn ihre mehrfache Ausführung dasselbe Ergebnis liefert wie ihre einmalige Ausführung. Du kannst sie einmal, zweimal oder zehnmal aufrufen – der Zustand am Ende ist immer derselbe.
Ein alltägliches Bild: Ein Lichtschalter mit der Aufschrift „Aus“ ist idempotent. Du drückst ihn einmal, das Licht ist aus. Du drückst ihn noch fünfmal, das Licht bleibt aus. Ein Schalter mit der Aufschrift „Umschalten“ ist dagegen nicht idempotent, denn jede Betätigung kehrt den Zustand um. „Setze den Bestellstatus auf bezahlt“ ist idempotent. „Ziehe 49 Euro vom Konto ab“ ist es nicht – jede Wiederholung kostet echtes Geld.
Der entscheidende Punkt: Nicht jede Operation ist von Natur aus idempotent, aber du kannst fast jede Operation idempotent machen. Genau das ist die Ingenieursaufgabe. Du musst nicht die Physik des Netzwerks ändern, du musst nur dafür sorgen, dass dein System eine Wiederholung als Wiederholung erkennt und nicht als neuen Vorgang behandelt.
Der Idempotenz-Key: der Fingerabdruck einer Aktion
Das wichtigste Werkzeug dafür ist der Idempotenz-Key. Das ist eine eindeutige Kennung, die der Auslöser einer Aktion mitgibt und die über alle Wiederholungen desselben Vorgangs hinweg stabil bleibt. Der Client erzeugt beim ersten Versuch zum Beispiel eine zufällige, eindeutige ID und schickt sie bei jedem Retry desselben Vorgangs unverändert mit.
Dein Server merkt sich beim ersten Eintreffen diesen Key zusammen mit dem Ergebnis. Kommt ein zweiter Request mit demselben Key, führst du die Aktion nicht erneut aus. Stattdessen gibst du das gespeicherte Ergebnis vom ersten Mal zurück. Der Client bekommt eine saubere Antwort, weiß gar nicht, dass etwas doppelt war, und die Zahlung wurde trotzdem nur einmal ausgeführt. Genau nach diesem Prinzip arbeiten übrigens die Zahlungs-APIs, mit denen du selbst integrierst – sie erwarten von dir einen solchen Key, damit ihre eigene Wiederholungslogik dich nicht doppelt belastet.
Deduplizierung bei Webhooks
Bei Webhooks liefert der Absender den Key oft schon frei Haus. Jedes Stripe-Ereignis hat eine eigene, eindeutige Ereignis-ID. Deine Aufgabe ist es, diese ID beim ersten Verarbeiten wegzuspeichern und bei jedem weiteren Eintreffen zu prüfen: Habe ich dieses Ereignis schon gesehen? Wenn ja, quittiere den Aufruf höflich mit einem Erfolg und tu ansonsten nichts. Diese Prüfung nennt man Deduplizierung, und sie ist die halbe Miete für stabile Webhook-Verarbeitung.
Praktisch legst du die gesehenen IDs in einem schnellen Speicher ab, zum Beispiel in Redis mit einer Lebensdauer von 24 Stunden, oder in einer Datenbanktabelle mit einer eindeutigen Beschränkung auf die Ereignis-ID. Der zweite Weg hat einen angenehmen Nebeneffekt: Die Datenbank selbst weist den doppelten Eintrag ab, du brauchst dich nicht auf eine fehlerfreie Prüf-dann-Schreib-Logik zu verlassen, die unter gleichzeitigen Aufrufen ihrerseits eine Lücke haben könnte.
Ein konkretes Praxisbeispiel: die doppelte Bestellung
Machen wir es greifbar. Ein Kunde kauft in einem Shop ein Software-Abo für 159 Euro im Jahr. Er klickt auf „Jetzt kaufen“, der Browser schickt den Request an die API, die API leitet die Zahlung beim Dienstleister ein. Die Zahlung geht durch, die Bestellung wird angelegt, eine Bestätigungsmail geht raus. Soweit der glückliche Fall.
Jetzt der reale Fall. Die Zahlung geht durch und die Bestellung wird angelegt, aber genau in diesem Moment bricht die Verbindung zwischen dem Server und dem Browser des Kunden ab. Der Kunde sieht keine Bestätigung, nur eine drehende Ladeanzeige und schließlich eine Fehlermeldung. Er ist verunsichert, wartet kurz und klickt noch einmal auf „Jetzt kaufen“. Ohne Schutz passiert nun genau das Falsche: Eine zweite Zahlung über 159 Euro wird eingeleitet, eine zweite Bestellung entsteht, eine zweite Bestätigungsmail geht raus. Der Kunde wurde doppelt belastet, obwohl er nur einmal kaufen wollte.
Und es kommt noch eine Ebene dazu. Parallel schickt der Zahlungsdienstleister einen Webhook, um die erfolgreiche Zahlung zu melden. Wegen einer kurzen Verzögerung auf deiner Seite quittierst du diesen Webhook nicht rechtzeitig, also schickt der Dienstleister ihn erneut. Wenn dein Webhook-Handler bei jedem Aufruf eine Bestellung anlegt, hast du jetzt aus einem einzigen Kaufwunsch drei oder vier Bestellungen erzeugt. Ein einziger unglücklicher Moment, multipliziert durch mehrere gutgemeinte Wiederholungsmechanismen.
So verhindert Idempotenz den Schaden
Mit einem Idempotenz-Key sieht dieselbe Geschichte anders aus. Der Browser erzeugt beim Öffnen der Kaufseite einen eindeutigen Key für genau diesen Kaufvorgang und schickt ihn bei jedem Klick mit – auch beim zweiten, genervten Klick. Der Server erkennt beim zweiten Request: Diesen Key habe ich bereits verarbeitet. Er leitet keine zweite Zahlung ein, legt keine zweite Bestellung an, sondern gibt einfach das Ergebnis vom ersten Mal zurück. Der Kunde sieht endlich seine Bestätigung, es wurde nur einmal belastet.
Beim Webhook greift die Deduplizierung. Der erste Aufruf legt die Bestellung an und merkt sich die Ereignis-ID. Der zweite Aufruf mit derselben ID wird sofort als bereits verarbeitet erkannt und mit einem freundlichen Erfolg quittiert, ohne etwas Neues anzulegen. Aus dem Chaos wird wieder eine einzige, korrekte Bestellung. Genau das ist der Wert von Idempotenz im Betrieb: Sie macht aus einem Bündel unvermeidlicher Wiederholungen ein sauberes, einmaliges Ergebnis.
Fehlertoleranz: Wiederholungen richtig gestalten
Idempotenz macht Wiederholungen sicher. Fehlertoleranz sorgt dafür, dass diese Wiederholungen klug ablaufen und dein System dabei nicht selbst umkippt. Beides gehört zusammen: Ohne Idempotenz sind Retries gefährlich, ohne durchdachte Retries ist Idempotenz nur die halbe Antwort.
Timeouts, die zum Fehler passen
Jeder Aufruf an einen anderen Dienst braucht ein Timeout. Ein Request, der ewig hängt, blockiert Ressourcen und reißt bei genug Last das ganze System mit. Aber das Timeout muss zur Aufgabe passen. Zu kurz gewählt, brichst du gesunde Vorgänge ab und erzeugst unnötige Wiederholungen. Zu lang gewählt, merkst du zu spät, dass ein Dienst nicht antwortet. Ein Timeout ist eine bewusste Entscheidung, kein Standardwert, den man einmal setzt und vergisst.
Backoff statt Trommelfeuer
Wenn ein Aufruf fehlschlägt, ist die naheliegende Reaktion, es sofort noch einmal zu versuchen. Das ist ein Fehler. Wenn ein Dienst gerade überlastet ist und tausend Clients gleichzeitig sofort erneut anklopfen, machst du die Überlastung schlimmer. Die Lösung heißt exponentielles Backoff: Nach dem ersten Fehlschlag wartest du kurz, nach dem zweiten länger, nach dem dritten noch länger. Zusätzlich streust du eine kleine Zufallskomponente ein, damit nicht alle Clients im selben Takt wiederkommen. So gibst du dem überlasteten Dienst Luft, sich zu erholen, statt ihn endgültig zu erschlagen.
Genauso wichtig: Nicht endlos wiederholen. Nach einer festgelegten Zahl an Versuchen gibst du auf und behandelst den Vorgang als fehlgeschlagen. Ein Retry, der nie endet, ist kein Retry, sondern ein Fehler, der sich als Robustheit verkleidet.
Dead-Letter-Queues für das, was nicht durchgeht
Manche Vorgänge scheitern auch nach dem letzten Versuch. Vielleicht ist die Nachricht kaputt, vielleicht ist ein abhängiger Dienst dauerhaft weg. Diese Vorgänge einfach wegzuwerfen wäre fahrlässig, denn dahinter kann eine echte Bestellung oder Zahlung stecken. Hier kommt die Dead-Letter-Queue ins Spiel: eine Warteschlange, in die alles wandert, was nach allen Versuchen nicht verarbeitet werden konnte. Nichts geht verloren, alles landet an einem sichtbaren Ort. Ein Mensch oder ein späterer Prozess kann sich die Einträge ansehen, die Ursache beheben und den Vorgang erneut anstoßen – und weil deine Verarbeitung idempotent ist, ist dieses erneute Anstoßen gefahrlos.
Wo Idempotenz besonders wichtig ist
Nicht jede Operation braucht denselben Aufwand. Eine reine Leseabfrage ist von Natur aus idempotent, da wiederholtes Lesen nichts verändert. Der Aufwand lohnt sich dort, wo Wiederholungen echten, oft irreversiblen Schaden anrichten.
- Zahlungen. Der offensichtlichste Fall. Eine doppelte Belastung ist nicht nur ein Ärgernis, sondern ein Vertrauensbruch und im Zweifel ein rechtliches Problem. Hier ist ein Idempotenz-Key nicht optional, sondern Pflicht.
- Bestellungen und Verträge. Doppelte Bestellungen erzeugen doppelte Lieferungen, doppelte Lizenzen und doppelten Support-Aufwand. Jeder Vorgang, der einen bindenden Zustand schafft, gehört abgesichert.
- E-Mails und Benachrichtigungen. Eine dreimal verschickte Bestätigungsmail wirkt unseriös und untergräbt das Vertrauen. Auch hier gilt: Der Versand muss an eine eindeutige Kennung des auslösenden Ereignisses gekoppelt sein, damit dieselbe Mail nicht mehrfach rausgeht.
- Bestandsänderungen. Lagerbestände, Kontingente, Guthaben. Alles, wo eine Zahl hoch- oder runtergezählt wird, ist besonders empfindlich, weil sich Fehler hier still aufsummieren.
So gehst du vor
Wenn du Idempotenz und Fehlertoleranz in einem bestehenden oder neuen System verankern willst, hilft ein klarer, überschaubarer Ablauf, statt alles auf einmal umzubauen.
- Schritt 1: Zustandsändernde Aktionen finden. Geh dein System durch und markiere jede Operation, die Geld bewegt, Datensätze anlegt oder etwas verschickt. Genau diese brauchen Aufmerksamkeit – reine Leseabfragen nicht.
- Schritt 2: Für jede Aktion einen stabilen Schlüssel definieren. Woran erkennst du, dass zwei Requests denselben Vorgang meinen? Bei Zahlungen ist es ein vom Client erzeugter Idempotenz-Key, bei Webhooks die Ereignis-ID des Absenders, bei internen Nachrichten eine eindeutige Nachrichten-ID.
- Schritt 3: Prüfen und speichern zusammenlegen. Verlass dich nicht auf „erst prüfen, ob schon vorhanden, dann anlegen“. Unter gleichzeitigen Aufrufen hat dieser Ansatz eine Lücke. Nutze stattdessen eine eindeutige Datenbank-Beschränkung, die den doppelten Schlüssel selbst abweist, oder einen atomaren Schreibvorgang.
- Schritt 4: Timeouts und Backoff für jeden externen Aufruf setzen. Kein Aufruf an einen fremden Dienst ohne Timeout. Kein Retry ohne exponentielles Backoff mit Zufallsanteil und einer Obergrenze für die Versuche.
- Schritt 5: Eine Dead-Letter-Queue einrichten. Definiere, wohin ein Vorgang wandert, der nach allen Versuchen scheitert, und wer sich darum kümmert. Ohne diesen Auffangort verlierst du im Ernstfall echte Daten.
- Schritt 6: Den Doppelfall testen. Schreibe gezielt Tests, die dieselbe Aktion zweimal auslösen, und prüfe, dass genau ein Ergebnis entsteht. Für Webhooks bedeutet das: dasselbe Ereignis zweimal einspielen und sicherstellen, dass nur eine Bestellung entsteht. Das ist der Test, der dich vor dem teuersten Bug bewahrt.
Fazit
Idempotenz ist kein Luxus für große Systeme, sondern die Antwort auf eine schlichte Tatsache: Im Betrieb wird jede Aktion früher oder später doppelt ausgelöst – durch Retries, durch Webhooks, durch abgebrochene Verbindungen. Die Frage ist nicht, ob das passiert, sondern ob dein System darauf vorbereitet ist. Mit Idempotenz-Keys, Deduplizierung, sinnvollen Timeouts, exponentiellem Backoff und einer Dead-Letter-Queue verwandelst du unvermeidliche Wiederholungen von einer Gefahr in einen Nicht-Vorfall. Der Kunde bezahlt einmal, bekommt eine Mail, sieht eine Bestellung – egal wie oft im Hintergrund etwas hakte. Genau das ist gutes Engineering-Handwerk: nicht das Vermeiden von Fehlern, sondern das Sorgen dafür, dass Fehler folgenlos bleiben.
Weitere Folgen dieser Serie findest du in der Serien-Übersicht. Wenn du ein System baust, das mit Zahlungen, Bestellungen oder externen Diensten arbeitet, und du sicherstellen willst, dass Wiederholungen keinen Schaden anrichten, unterstützen wir dich bei der Softwareentwicklung mit genau dieser Sorgfalt. Erzähl uns von deinem Vorhaben – ein kurzer Weg führt über unser Kontaktformular, und wir schauen gemeinsam, wo dein System heute schon robust ist und wo es noch doppelt zuschlagen könnte.