Nordlet

Dokumentation / Glossar

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

Wie eine wiederholte Anfrage eine doppelte Rechnung erzeugt, was ein Idempotenz-Schlüssel garantiert, welche Fehlerarten er behandeln muss und wie man ihn richtig implementiert.

Eine Operation ist idempotent, wenn ihre zweimalige Ausführung dieselbe Wirkung hat wie ihre einmalige Ausführung. Das Lesen eines Datensatzes ist von Natur aus idempotent. Das Erstellen einer Rechnung ist es nicht – rufen Sie die Funktion zweimal auf, haben Sie zwei Rechnungen, zwei Forderungen und einen Kunden, der fragt, warum ihm der Betrag zweimal in Rechnung gestellt wurde.

Diese Lücke ist entscheidend, denn Netzwerke fallen auf eine ganz bestimmte, unangenehme Weise aus: Die Anfrage ist erfolgreich, aber die Antwort geht verloren. Der Client erhält einen Timeout und kann nicht zwischen „nie angekommen“ und „angekommen, verarbeitet, Antwort verloren“ unterscheiden. Die einzig sicher erscheinende Option ist ein erneuter Versuch (Retry), und genau dieser erzeugt das Duplikat.

Warum die Buchhaltung es noch schlimmer macht

In den meisten Systemen ist ein Duplikat lediglich ein Ärgernis. In einem Hauptbuch (Ledger) potenziert sich das Problem:

  • Eine doppelte Rechnung verbraucht eine Nummer aus einem lückenlosen Nummernkreis, weshalb Löschen keine Option ist – Unveränderlichkeit bedeutet, dass zur Korrektur eine Gutschrift (Credit Note) erforderlich ist, also ein Dokument, das Ihr Kunde sieht.
  • Das Duplikat erzeugt Buchungssätze, sodass Umsatzerlöse, Umsatzsteuer und Forderungen zu hoch ausgewiesen werden, bis es jemandem auffällt.
  • Es fließt in die Umsatzsteuervoranmeldung und in Litauen in die i.SAF-Register ein.
  • Wenn es eine Zahlung ausgelöst hat, ist tatsächlich Geld geflossen.

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

Wie Idempotenz-Schlüssel funktionieren

Der Client generiert einen eindeutigen Schlüssel pro logischer Operation und sendet ihn mit der Anfrage mit:

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

Der Server garantiert daraufhin Folgendes:

  1. Erste Anfrage mit diesem Schlüssel – normal verarbeiten, das Ergebnis unter dem Schlüssel speichern.
  2. Erneuter Versuch mit demselben Schlüssel und demselben Payload – nicht erneut ausführen; die gespeicherte Antwort zurückgeben.
  3. Selber Schlüssel, anderer Payload – ablehnen. Der Schlüssel ist eine Behauptung, dass es sich um dieselbe Operation handelt; ein abweichender Body bedeutet, dass dies nicht der Fall ist, und die stille Rückgabe des alten Ergebnisses wäre schlimmer als ein Fehler.
  4. Erneuter Versuch, während das Original noch läuft – mit einem wiederholbaren Fehler (retryable error) ablehnen, anstatt es parallel (concurrently) auszuführen.

Punkt 4 ist derjenige, den naive Implementierungen übersehen. Ein Client, der nach 5 Sekunden in einen Timeout läuft und es nach 6 Sekunden erneut versucht, kollidiert mit seiner eigenen, noch laufenden (in-flight) Anfrage. Ohne einen Schutz vor Nebenläufigkeit (Concurrency Guard) werden beide ausgeführt, und man erhält genau das Duplikat, das der Mechanismus eigentlich verhindern sollte.

Schlüssel richtig generieren

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

  • Richtig: Eine UUID, die einmal generiert wird, wenn der Benutzer auf „Rechnung ausstellen“ klickt, und für jeden erneuten Versuch dieses Klicks wiederverwendet wird.
  • Richtig: Ein deterministischer Schlüssel, der aus Ihrer eigenen Domäne abgeleitet ist – order-1041-invoice.
  • Falsch: Eine neue UUID pro HTTP-Versuch. Jeder Retry erhält einen neuen Schlüssel, somit ist jeder Retry eine neue Operation, und man hat im Grunde nichts erreicht.
  • Falsch: Ein Hash des Payloads. Zwei legitimerweise identische Operationen kollidieren dann, und die zweite wird stillschweigend verschluckt.

Schlüssel benötigen eine Aufbewahrungsfrist (Retention Period) – lang genug, um realistische Retry-Fenster und Client-Ausfälle abzudecken, aber nicht für immer.

Was dadurch nicht gelöst wird

Idempotenz schützt vor der doppelten Ausführung derselben Anfrage. Sie schützt nicht vor:

  • Zwei verschiedenen Benutzern, die dieselbe Rechnung aus unterschiedlichen Sitzungen erstellen – dies ist ein Problem der Geschäftslogik, das durch Eindeutigkeitsbeschränkungen (Uniqueness Constraints) wie „nur 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. Dies ist zwar das gewünschte Verhalten, aber man sollte es sich bewusst machen.

Sie ergänzt Datenbank-Constraints, anstatt sie zu ersetzen. Das Hauptbuch von Nordlet nutzt beides: Idempotenz-Schlüssel stoppen wiederholte Anfragen, während Unique Indizes doppelte Abrechnungsläufe (Settlement Batches) pro Auszahlungs-ID, doppelte Eingangsrechnungen pro Lieferant und Belegnummer sowie doppelte Abschreibungsläufe pro Anlagegut und Periode verhindern.

Wie Nordlet es implementiert

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

Die erste Anfrage mit einem bestimmten Schlüssel wird ausgeführt und ihr Ergebnis gespeichert. Ein Retry mit demselben Schlüssel und demselben Payload gibt die gespeicherte Antwort zurück, ohne erneut ausgeführt zu werden, sodass die Rechnung exakt einmal erstellt wird, unabhängig davon, wie oft der Aufruf wiederholt wird.

Zwei Fehlerszenarien (Failure Modes) werden explizit und nicht stillschweigend behandelt:

  • Selber Schlüssel, anderer Payload → abgelehnt: "Idempotency-Key was already used with a different request payload." Dies fängt den wirklich gefährlichen Fall ab, in dem ein Client einen Schlüssel wiederverwendet, während er den Betrag ändert.
  • Selber Schlüssel, Original läuft noch → mit der Aufforderung zum späteren erneuten Versuch abgelehnt: "A request with this Idempotency-Key is still being processed, retry shortly." Keine parallele Doppelausführung.

Schlüssel sind pro Unternehmen (Company) isoliert (scoped), sodass zwei Mandanten (Tenants), die dieselbe UUID generieren, niemals kollidieren.

Die komplementäre Garantie gilt in die andere Richtung. Webhook-Zustellung erfolgt „At-Least-Once“ (mindestens einmal), was bedeutet, dass Ihr Endpunkt gelegentlich dasselbe Ereignis (Event) zweimal erhält – nach einem Timeout, bei dem die Zustellung tatsächlich erfolgreich war, oder bei einer erneuten Zustellung (Redelivery). Jedes Event trägt eine ID, und Ihr Handler sollte verarbeitete IDs aufzeichnen und Wiederholungen ignorieren. Idempotenz ist eine Eigenschaft, die beide Seiten einer Integration benötigen; der Leitfaden zu API-Konventionen behandelt die Request-Seite im Detail.

FAQ

Was ist ein Idempotenz-Schlüssel?

Ein vom Client generierter Identifikator, der mit einer Anfrage gesendet wird und es dem Server ermöglicht, einen erneuten Versuch derselben logischen Operation zu erkennen. Die erste Anfrage wird ausgeführt; erneute Versuche mit demselben Schlüssel geben das gespeicherte Ergebnis zurück, anstatt erneut ausgeführt zu werden.

Warum benötigen Buchhaltungs-APIs Idempotenz?

Weil ein doppelter Schreibvorgang ein echtes Dokument mit echten Konsequenzen erzeugt – eine zweite Rechnung, die eine fortlaufende Nummer verbraucht, ins Hauptbuch gebucht wird und in die Umsatzsteuervoranmeldung einfließt. Im Gegensatz zu einem doppelten Lesevorgang kann sie nicht einfach gelöscht werden; sie muss mit einer Gutschrift (Credit Note) korrigiert werden.

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

Nein – das macht den Mechanismus komplett nutzlos. Generieren Sie einen Schlüssel pro logischer Operation und verwenden Sie ihn für jeden erneuten Versuch dieser Operation wieder. Ein neuer Schlüssel pro Versuch bedeutet, dass jeder Versuch als neue Operation behandelt wird.

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

Die Anfrage wird abgelehnt. Der Schlüssel behauptet, dass es sich um dieselbe Operation handelt, also bedeutet ein geänderter Body, dass etwas nicht stimmt – höchstwahrscheinlich ein Fehler in der Schlüssel-Wiederverwendung (Key-Reuse Bug) auf dem Client. Das frühere Ergebnis stillschweigend zurückzugeben, wäre schlimmer.

Verhindert Idempotenz alle Duplikate?

Nein. Sie verhindert die doppelte Ausführung derselben Anfrage. Zwei verschiedene Benutzer, die unabhängig voneinander dieselbe Rechnung erstellen, haben zwei verschiedene Anfragen gestellt. Dies ist ein Problem der Geschäftslogik für Eindeutigkeitsbeschränkungen (Uniqueness Constraints) und nichts, was ein Idempotenz-Schlüssel erkennen kann.