Nordlet

← Dokumentation / Glossar

Was ist Idempotenz? Warum Buchhaltungs- und Zahlungs-APIs sie benötigen

Wie ein wiederholter Request eine doppelte Rechnung erzeugt, was ein Idempotenzschlüssel garantiert, welche Fehlerzustände er abfangen muss und wie Sie ihn korrekt implementieren.

Eine Operation ist idempotent, wenn ihre zweimalige Ausführung dieselbe Wirkung hat wie ihre einmalige. Das Lesen eines Datensatzes ist naturgemäß idempotent. Das Erstellen einer Rechnung ist es nicht – rufen Sie diesen Vorgang zweimal auf, erhalten Sie zwei Rechnungen, zwei Forderungen und einen Kunden, der sich erkundigt, warum ihm der Betrag zweimal berechnet wurde.

Diese Lücke ist kritisch, da Netzwerke auf eine ganz bestimmte, unangenehme Art versagen: Der Request ist erfolgreich, aber die Antwort geht verloren. Der Client stellt einen Timeout fest und kann nicht unterscheiden, ob die Nachricht „niemals angekommen“ ist oder „angekommen ist, verarbeitet wurde und die Antwort verloren ging“. Seine einzige sichere Option scheint eine Wiederholung zu sein, und genau diese Wiederholung erzeugt das Duplikat.

Warum das im Rechnungswesen noch kritischer ist

In den meisten Systemen ist ein Duplikat lediglich ein Ärgernis. In einem Hauptbuch (Ledger) führt es jedoch zu einer Kettenreaktion von Problemen:

  • Eine doppelte Rechnung verbraucht eine Nummer aus einer lückenlosen Sequenz (gapless sequence). Ein einfaches Löschen kommt daher nicht infrage – aufgrund der Unveränderlichkeit erfordert eine Korrektur eine Gutschrift, also ein Dokument, das Ihr Kunde zu Gesicht bekommt.
  • Das Duplikat generiert Buchungssätze, sodass Umsätze, die Umsatzsteuer und Forderungen so lange zu hoch ausgewiesen werden, bis es jemand bemerkt.
  • Es fließt in die Umsatzsteuervoranmeldung und in Litauen in die i.SAF-Register ein.
  • Wenn dadurch eine Zahlung ausgelöst wurde, ist tatsächlich Geld geflossenen.

Der Ansatz „Einfach auf Duplikate prüfen“ funktioniert ebenfalls nicht, da zwei völlig eigenständige Rechnungen an denselben Kunden über denselben Betrag am selben Tag durchaus normal sind. Das System kann die Absicht nicht aus dem Inhalt ableiten – der Client muss sie explizit mitteilen.

Funktionsweise von Idempotenzschlüsseln

Der Client generiert für jede logische Operation einen eindeutigen Schlüssel und sendet diesen mit dem Request:

POST /v1/sales/invoices/create
Idempotency-Key: 8f14e45f-ea1b-4c2b-9f3a-2d7c1e0b5a91

Der Server garantiert daraufhin Folgendes:

  1. Erster Request mit diesem Schlüssel – Wird normal verarbeitet; das Ergebnis wird unter dem Schlüssel gespeichert.
  2. Wiederholung mit demselben Schlüssel und demselben Payload – Wird nicht erneut ausgeführt; stattdessen wird die gespeicherte Antwort zurückgegeben.
  3. Derselbe Schlüssel, aber ein anderer Payload – Wird abgewiesen. Der Schlüssel ist die Zusicherung, dass es sich um dieselbe Operation handelt. Ein abweichender Body besagt das Gegenteil, und das stumme Zurückgeben des alten Ergebnisses wäre schlimmer als ein Fehler.
  4. Wiederholung, während die ursprüngliche Operation noch läuft – Wird mit einem wiederholbaren Fehler abgewiesen, anstatt sie parallel auszuführen.

Punkt 4 ist der Aspekt, der bei naiven Implementierungen oft übersehen wird. Ein Client, der nach 5 Sekunden in einen Timeout läuft und nach 6 Sekunden einen Retry sendet, kollidiert mit seinem eigenen, noch laufenden Request. Ohne Schutzmechanismus für die Nebenläufigkeit (concurrency guard) werden beide Operationen ausgeführt, und Sie erhalten genau das Duplikat, das der Mechanismus verhindern sollte.

Korrekte Generierung von Schlüsseln

Der Schlüssel muss die Operation identifizieren, nicht den jeweiligen Versuch:

  • Richtig: Eine UUID, die einmalig generiert wird, wenn der Nutzer auf „Rechnung ausstellen“ klickt, und für jeden Retry dieses Klicks wiederverwendet wird.
  • Richtig: Ein deterministischer Schlüssel, der sich aus Ihrer eigenen Domäne ableitet – order-1041-invoice.
  • Falsch: Eine neue UUID pro HTTP-Versuch. Jeder Retry erhält einen neuen Schlüssel, wodurch jeder Retry eine neue Operation darstellt und Sie de facto keine Idempotenz implementiert haben.
  • Falsch: Ein Hash des Payloads. In diesem Fall kollidieren zwei legitime, identische Operationen miteinander, und die zweite wird stillschweigend verschluckt.

Schlüssel benötigen eine Aufbewahrungsfrist (retention period) – lang genug, um realistische Wiederholungsfenster und Ausfälle auf Clientseite abzudecken, aber nicht unbegrenzt.

Was Idempotenz nicht löst

Idempotenz schützt vor der doppelten Ausführung desselben Requests. Sie schützt nicht vor:

  • Zwei verschiedenen Nutzern, die aus unterschiedlichen Sitzungen heraus dieselbe Rechnung erstellen – dies ist ein Problem der Geschäftslogik, das durch Eindeutigkeitsbeschränkungen (uniqueness constraints) wie etwa „eine Rechnung pro Bestellung“ gelöst wird.
  • Logischen Duplikaten mit unterschiedlichen Schlüsseln.
  • Nicht-Determinismus innerhalb der Operation – wenn der Handler einen Zeitstempel oder eine Nummer generiert, erfasst die gespeicherte Antwort den ersten Wert. Das entspricht dem gewünschten Verhalten, sollte aber verstanden werden.

Idempotenz ergänzt Datenbankbeschränkungen, anstatt sie zu ersetzen. Das Ledger von Nordlet nutzt beides: Idempotenzschlüssel stoppen wiederholte Requests, während eindeutige Indizes (unique indexes) doppelte Abrechnungsstapel pro Auszahlungs-ID, doppelte Eingangsrechnungen pro Lieferant und Belegnummer sowie doppelte Abschreibungsläufe pro Anlagegut und Periode verhindern.

Wie Nordlet Idempotenz implementiert

Senden Sie bei jeder zustandsändernden Anfrage einen Idempotency-Key-Header – einen nicht leeren String mit maximal 255 Zeichen.

Der erste Request mit einem bestimmten Schlüssel wird ausgeführt, und sein Ergebnis wird gespeichert. Ein Retry mit demselben Schlüssel und demselben Payload liefert die gespeicherte Antwort zurück, ohne den Vorgang erneut auszuführen. Dadurch wird die Rechnung unabhängig von der Anzahl der Wiederholungen exakt einmal erstellt.

Zwei Fehlerzustände werden explizit statt stillschweigend behandelt:

  • Derselbe Schlüssel, aber anderer Payload → Abgelehnt mit der Meldung: "Idempotency-Key was already used with a different request payload." Dies fängt den gefährlichen Fall ab, in dem ein Client einen Schlüssel wiederverwendet, aber den Betrag ändert.
  • Derselbe Schlüssel, während das Original noch läuft → Abgelehnt als wiederholbar mit der Meldung: "A request with this Idempotency-Key is still being processed, retry shortly." Dies verhindert parallele Doppelausführungen.

Schlüssel sind pro Mandant (company) isoliert, sodass zwei Tenants, die zufällig dieselbe UUID generieren, niemals miteinander kollidieren.

Die ergänzende Garantie greift in die entgegengesetzte Richtung. Webhook-Zustellungen erfolgen nach dem Prinzip „At-least-once“ (mindestens einmal). Das bedeutet, dass Ihr Endpoint gelegentlich dasselbe Event zweimal erhält – nach einem Timeout, bei dem die Zustellung tatsächlich erfolgreich war, oder im Falle einer erneuten Auslieferung. Jedes Event enthält eine ID, und Ihr Handler sollte verarbeitete IDs protokollieren und Wiederholungen ignorieren. Idempotenz ist eine Eigenschaft, die beide Seiten einer Integration betreffen muss. Der Leitfaden zu API-Konventionen beleuchtet die Request-Seite im Detail.

FAQ

Was ist ein Idempotenzschlüssel?

Ein vom Client generierter Bezeichner, der mit einem Request gesendet wird und den Server in die Lage versetzt, die Wiederholung derselben logischen Operation zu erkennen. Der erste Request wird ausgeführt; Retries mit demselben Schlüssel geben das gespeicherte Ergebnis zurück, anstatt die Operation erneut auszuführen.

Warum benötigen Buchhaltungs-APIs Idempotenz?

Weil ein doppeltes Schreiben ein reales Dokument mit realen Konsequenzen erzeugt – eine zweite Rechnung, die eine fortlaufende Nummer verbraucht, ins Hauptbuch bucht und in eine Umsatzsteuererklärung einfließt. Im Gegensatz zu einem doppelten Lesevorgang kann sie nicht einfach gelöscht werden, sondern muss durch eine Gutschrift korrigiert werden.

Sollte ich für jeden Retry einen neuen Schlüssel generieren?

Nein – das untergräbt den Mechanismus vollständig. Generieren Sie genau einen Schlüssel pro logischer Operation und verwenden Sie diesen für jeden Retry dieses Vorgangs. Ein neuer Schlüssel pro Versuch führt dazu, dass jeder Versuch als neue Operation behandelt wird.

Was passiert, wenn ich einen Schlüssel mit einem anderen Payload wiederverwende?

Der Request wird abgelehnt. Der Schlüssel versichert, dass es sich um dieselbe Operation handelt. Ein veränderter Body weist daher auf einen Fehler hin – höchstwahrscheinlich auf einen Fehler bei der Schlüsselwiederverwendung auf Clientseite. Das stumme Zurückgeben des vorherigen Ergebnisses wäre die schlechtere Wahl.

Verhindert Idempotenz sämtliche Duplikate?

Nein. Sie verhindert die doppelte Ausführung desselben Requests. Wenn zwei verschiedene Benutzer unabhängig voneinander dieselbe Rechnung erstellen, haben sie zwei unterschiedliche Requests abgesetzt. Dies ist ein Problem der Geschäftslogik für Eindeutigkeitsregeln und kann von einem Idempotenzschlüssel nicht erkannt werden.