Nordlet

Blog

Testplan für die Vorproduktion einer Buchhaltungs-API

Ein Leitfaden zur Zuverlässigkeit für Entwicklerteams: doppelte Schreibvorgänge, Wiederverwendung von Idempotenz-Schlüsseln, Webhook-Signaturen, außer der Reihe eintreffende Events, Dezimalbeträge, Scopes, Periodensperren und Recovery.

Nordlet Team · · 16 Min. Lesezeit

Ein 201 Created von einer Buchhaltungs-API sagt Ihnen fast gar nichts. Es bestätigt lediglich, dass der Server geantwortet hat. Es bestätigt nicht, dass exakt eine Rechnung existiert, dass Soll und Haben ausgeglichen sind, dass die Belegnummer nur einmal vergeben wurde oder dass ein erneuter Versuch nach einem Verbindungsabbruch keine Zweitbuchung (Duplikat) erzeugt. Die meisten Duplikate in produktiven Buchhaltungssystemen entstehen genau in dieser Lücke: eine Änderungsoperation (Mutation), die auf dem Server festgeschrieben (committed) wurde, während der Client mit unklarem Ergebnis wartete und den Vorgang unbemerkt mit einem neuen Schlüssel wiederholte.

Dieser Plan richtet sich an Teams, die eine Buchhaltungs-API evaluieren, bevor sie diese in echte Buchhaltungssysteme integrieren. Der Fokus liegt auf dem Fehlerverhalten, nicht auf dem Happy Path. Jeder der unten stehenden Bereiche legt dar, was zu testen ist, welche Zusicherungen (Assertions) getroffen werden müssen und was als bestanden gilt. Der Implementierungskontext ist hier Nordlet, dessen API bereichsbezogene (scoped) Bearer-Schlüssel, Beträge als Dezimal-Strings, Idempotenz-Schlüssel und signierte Webhooks verwendet. Für die genaue Endpunkt-Syntax und Response-Verträge lesen Sie bitte die Seiten API-Konventionen und Erste Schritte, anstatt sich auf bloße Annahmen zu verlassen.

Was „Bestanden“ eigentlich bedeutet

Ein Test beweist nur dann die Sicherheit einer Buchhaltungsintegration, wenn er Semantik und nicht nur Statuscodes verifiziert. Für jedes Szenario sollte die Test-Suite Folgendes bestätigen:

  • Betriebswirtschaftliche Auswirkung: Es existiert exakt ein Kunde, eine Rechnung, ein Buchungssatz, eine Nummernvergabe oder eine Zahlungsstatusänderung.
  • Buchhalterische Integrität: Soll und Haben sind ausgeglichen, und die Währungspräzision übersteht die Serialisierung.
  • Replay-Verhalten (Wiederholungsverhalten): Ein Retry (Wiederholungsversuch) liefert das ursprüngliche Ergebnis anstelle eines neuen Datensatzes.
  • Dauerhaftigkeit (Persistenz): Audit-Protokolle und Webhook-Events existieren, sobald die Operation festgeschrieben ist.
  • Fehler ohne Nebenwirkungen: Abgelehnte Anfragen verbrauchen keine Belegnummern und schreiben keine unvollständigen Journalzeilen.
  • Recovery (Wiederherstellung): Ein vorübergehender Fehler kann ohne Duplizierung oder dauerhaften Datenverlust wiederholt werden.

Die API-Richtlinien von Google ziehen hier eine klare Grenze: Nur Operationen, deren wiederholte Ausführung denselben Endzustand hinterlässt, können sicher wiederholt werden. Eine API sollte klar identifizieren, welche Operationen idempotent sind, anstatt jeden Fehler blind zu wiederholen.

Aufbau einer isolierten, beobachtbaren Umgebung

Nutzen Sie ein Testunternehmen (Sandbox-Mandanten), keine echten Buchhaltungsdaten. Ein Nordlet-Sandbox-Unternehmen nutzt dieselben Module und dasselbe API-Verhalten wie ein echtes Unternehmen und ist als Testdatenbestand gekennzeichnet. Das macht es sicher für massenhafte Duplikats- und Fehlertests. Diese Kennzeichnung ist nach der Erstellung unveränderlich; echte Bücher können also nicht nachträglich zu Testdaten umdeklariert werden. Beachten Sie, dass die Nutzung der Sandbox genau wie die Nutzung in einem echten Unternehmen gemessen und abgerechnet wird. Passen Sie Ihre Volumentests daher an die Preisgestaltung Ihres aktuellen Tarifs an.

Bevor Sie einen Test schreiben, bereiten Sie Folgendes vor:

  • Ein dediziertes Sandbox-Unternehmen mit isSandbox: true.
  • Einen Test-API-Schlüssel, dessen Scope (Geltungsbereich) strikt auf die getesteten Module beschränkt ist.
  • Einen Webhook-Endpunkt oder lokalen Tunnel, der den rohen Request-Body (Raw Body) unberührt lässt.
  • Vorab angelegte (seeded) Kunden, Lieferanten und einen bekannten Kontenplan.
  • Mindestens zwei Buchungsperioden: eine offene, eine gesperrte.
  • Eine Test-Uhr (Test Clock) oder ein Datums-Fixture, falls die Implementierung dies unterstützt.
  • Einen lokalen Datenspeicher, der jeden Anfrageversuch, Idempotenz-Schlüssel, Response-Hash, jede Webhook-Zustellung und jedes Abstimmungsergebnis (Reconciliation) protokolliert.

Führen Sie Tests zu Duplikaten nicht auf gemeinsam genutzten Staging-Daten aus. Ein verirrter Kunde oder eine Rechnung aus einem früheren Durchlauf machen spätere Assertions mehrdeutig.

Ihre Fixture sollte für jede Änderungsoperation Methode, Endpunkt, den vollständigen Body, den Idempotenz-Schlüssel, den Key-Scope, eine Client-Korrelations-ID, Start- und End-Zeitstempel, den Response-Status, Header, den Body, die Request-ID sowie die Information erfassen, ob die Verbindung vor Eintreffen der Response abgebrochen ist. Verwenden Sie für Geldbeträge fixe Dezimal-Strings wie "75.0000". Nordlet repräsentiert Währungswerte als Dezimal-Strings mit bis zu vier Nachkommastellen; die Beispielrechnung nutzt exakt dieses Format.

Die andere Hälfte der Vorbereitung ist das gezielte Einsteuern von Fehlern (Failure Injection). Ihr Test-Harness sollte in der Lage sein, eine Response zu verzögern, eine TCP-Verbindung zu trennen (nachdem der Server die Request erhalten hat), synthetische 429/500/502/503/504-Responses zurückzugeben, einen Non-2xx-Status vom Webhook-Empfänger zu senden, denselben Webhook zweimal zuzustellen, Events in falscher Reihenfolge zu senden, ein Byte des Webhook-Bodys zu beschädigen, die Payload eines Idempotenz-Schlüssels bei gleichbleibendem Schlüssel zu ändern, zwei identische Requests parallel auszuführen und eine Buchungsperiode zwischen Vorbereitung und Buchung zu sperren. Diese Szenarien sind mehr wert als hundert Happy-Path-Beispiele.

Fixierung des API-Vertrags vor der Implementierung von Retry-Logik

Generieren oder laden Sie das OpenAPI-Schema herunter und vergleichen Sie es mit Ihrem Client. Nordlet veröffentlicht seine OpenAPI-Spezifikation in der API-Referenz und generiert seine typisierten SDKs aus derselben Spezifikation. Schemagesteuerte Tests schlagen daher manuell geschriebene Vermutungen um Längen.

Überprüfen Sie Pflichtfelder und Typen, Dezimal-String-Felder, Datums- und Zeitstempelformate, erlaubte Aktionen und Scopes, die Struktur des Fehler-Envelopes, Webhook-Event-Schemata, welche Änderungsoperationen einen Idempotenz-Schlüssel akzeptieren und welche Response-Felder das erstellte Objekt identifizieren.

Erfolgskriterium. Der Client weist ungültige Payloads lokal ab, sofern das Schema dies zulässt. Die Responses des Servers entsprechen dem dokumentierten Schema. Ungültige Dezimalzahlen, fehlerhafte Daten (Dates) und fehlende Pflichtfelder erzeugen strukturierte Validierungsfehler. Bei einem deterministischen Validierungsfehler wird kein Retry versucht.

Zu vermeidende Fehlerquelle. Ein Client parst "121.0000" in einen binären Float und serialisiert später einen leicht abweichenden Betrag. Ganze Zahlen funktionieren; Mehrwertsteuerzeilen und gebrochene Mengen schlagen fehl. Testen Sie Geldbeträge durchgängig als Strings.

Autorisierung und Mandantentrennung (Tenant Isolation)

Verwenden Sie separate Schlüssel für Lese- und Schreibzugriffe. Testen Sie dann die gesamte Matrix: fehlender Schlüssel, ungültiger Schlüssel, gültiger Schlüssel mit unzureichendem Modul-Scope, gültiger Schlüssel mit korrektem Scope, ein Schlüssel eines anderen Unternehmens und der Versuch, ein Objekt aus einem fremden Unternehmen zu lesen.

Zusicherungen (Assertions):

  • Ein fehlender oder ungültiger Key liefert 401.
  • Ein gültiger Key ohne die erforderliche Berechtigung liefert 403.
  • Für keinen der Fehler wird ein buchhalterischer Nebeneffekt erzeugt.
  • Ein erfolgreicher Request kann die Datensätze eines anderen Unternehmens weder lesen noch verändern.
  • Security-Fehler werden niemals automatisch wiederholt.

Erfolgskriterium. Sie können die Korrektheit der Autorisierung und der Mandantentrennung durch anschließende Leseanfragen nachweisen, nicht nur durch Statuscodes.

Nachweis einer fehlerfreien Transaktion

Führen Sie einen kurzen, deterministischen Ablauf durch: Erstellen oder finden Sie einen Testkunden, erstellen Sie eine Rechnung mit Dezimal-String-Beträgen, stellen Sie diese aus (issue), lesen Sie die Rechnung und ihre buchhalterischen Auswirkungen aus, lesen Sie das Audit-Protokoll und empfangen Sie den Webhook. Das Nordlet Getting-Started-Beispiel zeigt, wie bei der Rechnungsausstellung in derselben Transaktion eine lückenlose Nummer vergeben, geprüft wird, ob die Periode offen ist, und ein ausgeglichener Buchungssatz erstellt wird.

Stellen Sie sicher, dass die Rechnung den erwarteten Zustand erreicht, die Belegnummer exakt einmal vergeben wird, Soll und Haben ausgeglichen sind, die Mehrwertsteuer und die Zeilensummen der erwarteten kaufmännischen Rundung (Half-up) entsprechen, das Audit-Protokoll den Akteur und die Änderung benennt, der Webhook letztendlich zugestellt wird und die API-Response sowie ein anschließender Read bezüglich Rechnungs-ID und Status übereinstimmen.

Ein wichtiger Unterschied, den man sich verinnerlichen sollte: Das Eintreffen eines Webhooks ist kein Beweis dafür, dass die Änderungsoperation erfolgreich verbucht (committed) wurde. Die API-Response in Kombination mit einem anschließenden Read-back sind Ihre primären Transaktionszusicherungen. Der Webhook beweist lediglich die nachgelagerte Benachrichtigung und wird separat getestet.

Idempotenz: gleicher Schlüssel, gleicher Body

Generieren Sie einen Schlüssel pro logischer Geschäftsoperation, nicht pro Netzwerkversuch. Senden Sie die Änderungsoperation mit Schlüssel K und Body B, speichern Sie die erstellte ID und senden Sie exakt dieselbe Anfrage erneut.

Stellen Sie sicher, dass die zweite Response dieselbe Operation repräsentiert, die Objekt-ID identisch ist und nur ein einziges Geschäftsobjekt, eine Buchung, eine Belegnummer und eine buchhalterische Auswirkung existiert. Die wiederholte (replayed) Response sollte den dokumentierten Replay-Header enthalten – im Fall von Nordlet x-idempotent-replay: true – und der Revisionspfad (Audit Trail) sollte keine zweite Änderungsoperation aufweisen.

Zwei Speicherregeln verändern das, was Ihre Retry-Schleife erwarten sollte. Testen Sie diese daher direkt: Ein gespeicherter 4xx-Fehler wird wie jede andere Response erneut ausgespielt, während ein 5xx-Fehler überhaupt nicht gespeichert wird. Ein Retry nach einem Serverfehler führt die Operation also erneut aus, anstatt sie bloß aus dem Cache zu laden. Schlüssel verfallen auch (bei Nordlet nach 24 Stunden), was den Zeitraum begrenzt, in dem ein Replay zur Verfügung steht.

Die Nordlet-Dokumentation garantiert, dass das Hinzufügen eines Idempotency-Key-Headers verhindert, dass ein wiederholter Request ein Duplikat erstellt. Die API-Konventionen definieren zudem den exakten Replay-Header und die Anforderung an eine Byte-für-Byte identische Response. Dieses Prinzip entspricht dem veröffentlichten Modell von Stripe: Ein Schlüssel identifiziert die logische Operation, Wiederholungen liefern das gespeicherte Ergebnis, und ein Schlüssel, der mit abweichenden Parametern wiederverwendet wird, wird abgelehnt, anstatt heimlich Änderungen vorzunehmen.

Erfolgskriterium. Es existiert exakt ein finanzieller Statusübergang, unabhängig davon, wie oft der Client dieselbe Anfrage innerhalb des Replay-Zeitfensters sendet.

Idempotenz: gleicher Schlüssel, geänderter Body

Senden Sie den ursprünglichen Request mit Schlüssel K und Body B1, anschließend einen zweiten Request mit Schlüssel K und Body B2, bei dem sich mindestens ein Betrag, Kunde, Datum oder eine Rechnungszeile unterscheidet.

Erwartetes Ergebnis: Der Request wird mit dem dokumentierten Key-Reuse-Fehler abgelehnt – Nordlet liefert 422 idempotency_key_reuse – der ursprüngliche Datensatz bleibt unangetastet, es taucht kein zweiter Datensatz, keine zweite Nummer und kein zweiter Audit-Eintrag auf, und der Client „repariert“ dies nicht, indem er einfach einen neuen Schlüssel generiert. Würde B2 stillschweigend akzeptiert werden, wären Retry-Schlüssel unsicher.

Gleichzeitige (konkurrierende) Duplikatsanfragen

Feuern Sie zwei identische Requests mit demselben Schlüssel im selben Moment ab. Je nach dokumentiertem Verhalten wird der eine ausgeführt, während der andere einen In-Progress-Konflikt erhält – Nordlet liefert 409 idempotency_in_progress – oder beide erhalten letztendlich dasselbe Ergebnis.

Stellen Sie sicher, dass beide Aufrufe keine voneinander unabhängigen finanziellen Auswirkungen erzeugen können, höchstens eine Belegnummer vergeben wird, ein temporärer Konflikt mit demselben anstatt einem neuen Schlüssel wiederholt wird, die finale Ressource vollständig und lesbar ist und der Idempotenz-Datensatz nicht dauerhaft blockiert bleibt.

Zu vermeidende Fehlerquelle. Eine Retry-Bibliothek, die pro Versuch eine neue UUID generiert, macht aus einer logischen Operation mehrere unabhängige Schreibvorgänge und hebelt die Idempotenz komplett aus.

Das ungewisse Ergebnis nach einem Verbindungsabbruch

Dies ist die wichtigste Produktionssimulation überhaupt. Senden Sie eine Änderungsoperation mit dem Schlüssel K, lassen Sie den Server diese festschreiben (commit), trennen Sie die Verbindung, bevor die Response eintrifft, verbuchen Sie das Ergebnis lokal als unbekannt, versuchen Sie es dann mit demselben Schlüssel K erneut und stimmen Sie den Vorgang ab (Reconciliation), indem Sie die Ressource und die Audit-Daten abfragen.

Stellen Sie sicher, dass der Retry die ursprüngliche oder dokumentierte Replay-Response liefert, exakt ein buchhalterischer Nebeneffekt existiert, der Client die Operation erst nach einem erfolgreichen Replay oder einem Reconciliation-Read als abgeschlossen markiert und kein neuer Schlüssel generiert wird. Ein Timeout bedeutet nicht „es ist nichts passiert“. Stripe beschreibt Idempotenz explizit als Schutz gegen Verbindungsfehler, bei denen der Server eine Änderung möglicherweise verarbeitet hat, bevor der Client die Response empfangen konnte.

Eine konservative Retry-Strategie

Retry-Entscheidungen hängen sowohl vom Fehler als auch davon ab, ob die Operation idempotent ist, und nicht allein von der HTTP-Methode.

In der Regel wiederholbar, derselbe Schlüssel wird beibehalten: Verbindungsabbruch oder Timeout nach einer idempotenten Änderungsoperation, 408, 429 (sofern retry-after respektiert wird) sowie 502/503/504. Eine dokumentierte In-Progress-Idempotenz-Response kann nach einer kurzen Verzögerung wiederholt werden.

Ohne Abstimmung (Reconciliation) nicht wiederholbar: 400/422 Validierungsfehler, 401/403 Authentifizierungsfehler, 404 für eine fehlende Ressource, fachliche 409-Konflikte, die nichts mit einem in Bearbeitung befindlichen (in-flight) Key zu tun haben, geänderte Payloads bei einem existierenden Schlüssel und abgelehnte Anfragen wegen Periodensperren.

Überprüfen Sie einen begrenzten exponentiellen Backoff mit Jitter, explizite maximale Versuche und verstrichene Zeit, dass retry-after bei Rate-Limits respektiert wird, dass derselbe Schlüssel bei Retrys derselben Operation beibehalten wird, dass für jede neue Operation ein neuer Schlüssel verwendet wird und dass Logs den ursprünglichen Versuch sauber von Retrys trennen.

Testen Sie das Rate-Limiting separat. Überschreiten Sie den dokumentierten Schwellenwert – Nordlet erlaubt standardmäßig 300 Requests pro Minute pro API-Key, und einige nicht authentifizierte Endpunkte haben eigene, niedrigere Limits pro IP-Adresse – und bestätigen Sie dann, dass der Server die Rate-Limit-Response liefert. Der Client muss retry-after als Verzögerung und nicht als bloßen Fehler parsen, er muss pausieren anstatt den Server zu bombardieren, und ein gedrosselter Retry einer Änderungsoperation muss den ursprünglichen Schlüssel wiederverwenden. Die x-ratelimit-*-Header geben Auskunft über das verbleibende Budget, was sich hervorragend für eine Pre-Assertion eignet. Halten Sie Rate-Limit-Tests von Finanz-Assertions fern, es sei denn, Ihre Test-Fixture kann zweifelsfrei zwischen einem blockierten und einem festgeschriebenen (committed) Versuch unterscheiden.

Verifizierung von Webhook-Signaturen

Bauen Sie einen Empfänger, der die rohen Bytes (Raw Body) intakt lässt. Für jedes Event: Erfassen Sie den rohen Body, lesen Sie den x-nordlet-signature-Header, berechnen Sie den erwarteten HMAC-SHA256 mit dem Subscription-Secret, vergleichen Sie diesen über eine zeitkonstante Funktion (Constant-Time-Vergleich), parsen Sie das JSON erst nach erfolgreicher Verifizierung, protokollieren Sie die Zustellung und geben Sie zügig einen 2xx-Status nach dauerhafter (durable) Annahme zurück. Nordlet signiert einen HMAC über den rohen Body, sendet ihn als sha256=<hex> und wiederholt Zustellungen mit exponentiellem Backoff. Die Webhook-Richtlinien von Stripe unterstreichen ebenfalls die Prüfung des unveränderten Raw Bodys mit einem Constant-Time-Vergleich.

Führen Sie Negativtests durch: korrekter Body und Secret, korrekter Body mit falschem Secret, Änderung eines einzigen Bytes im Body, neu serialisiertes JSON mit veränderten Leerzeichen, fehlender Header, fehlerhaft formatierte Signatur, erneutes Abspielen einer alten Zustellung (falls das Schema einen Zeitstempel enthält) und eine gültige Signatur, die an die falsche Subscription gesendet wurde.

Erfolgskriterien. Nur der korrekte Raw Body und das korrekte Secret werden akzeptiert. Ungültige Signaturen erhalten einen Non-2xx-Status. Ungültige Payloads landen niemals in der Buchhaltungs-Warteschlange. Secrets tauchen nicht in gewöhnlichen Logs auf. Der Handler bestätigt den Empfang erst, nachdem das Event dauerhaft gesichert wurde.

Doppelte und außer der Reihe eintreffende Webhooks (Out-of-Order Webhooks)

Die Zuverlässigkeit der Zustellung ist ein anderes Problem als die API-Idempotenz, und der Empfänger muss ebenfalls idempotent arbeiten. Stellen Sie dasselbe Event zweimal zu, stellen Sie ein sale_invoice.paid-Event vor einem sale_invoice.issued-Event zu, geben Sie einen 500-Status zurück, nachdem das Event gespeichert wurde, aber bevor die Response gesendet wird, und spielen Sie ein Event ab, das der Consumer bereits verarbeitet hat.

Stellen Sie sicher, dass doppelte Zustellungen nicht zu doppelten Zahlungen, Statusänderungen oder Journalbuchungen führen. Der Deduplizierungsschlüssel sollte die Zustellungs- oder Event-ID des Providers verwenden, sofern vorhanden (oder einen dokumentierten zusammengesetzten Schlüssel, niemals einen willkürlichen Zeitstempel). Außer der Reihe eintreffende Events dürfen neuere Zustände niemals mit älteren überschreiben. Der Consumer muss in der Lage sein, den aktuellen Status von der API abzurufen, wenn die Reihenfolge allein nicht aussagekräftig genug ist. Fehlgeschlagene Versuche des Consumers müssen über eine Warteschlange oder eine Dead-Letter-Queue laufen. Stripe warnt ausdrücklich davor, dass die Zustellreihenfolge nicht garantiert ist, und empfiehlt die Speicherung verarbeiteter Event-IDs.

Halten Sie den Endpunkt schlank: Format validieren, Signatur verifizieren, persistieren oder in eine Queue schieben, bestätigen (acknowledge), 2xx zurückgeben. Abstimmung (Reconciliation) und Benachrichtigungen gehören in die asynchrone Verarbeitung. Testen Sie dies, indem Sie die nachgelagerte Verarbeitung über das Zustellungs-Timeout hinaus blockieren (sleep); ein korrekter Empfänger bestätigt dennoch sofort nach erfolgreichem Einreihen in die Queue.

Offene und gesperrte Buchungsperioden

Nordlet erzwingt Periodensperren zum Zeitpunkt der Verbuchung, sowohl bei manuellen Journalbuchungen, Beleg-Workflows als auch Importen. Testen Sie diese Kontrollmechanismen also an mehr als einer Stelle. Erstellen Sie identische Dokumente mit Datum in einer offenen Periode, in einer gesperrten Periode, an der Grenze kurz vor der Sperrung, am ersten Tag der nächsten offenen Periode sowie mit einem gültigen Belegdatum, aber ungültigem Buchungsdatum (falls die API beides trennt).

Für den Fall der gesperrten Periode stellen Sie sicher, dass der Request mit dem dokumentierten Periodensperren-Fehler abgelehnt wird – bei Nordlet ein 409 conflict – und dass keine Journalzeilen geschrieben, keine Belegnummern verbraucht, kein Beleg auf „verbucht“ (posted) gesetzt und kein Erfolgs-Webhook ausgelöst wird. Die Version in der offenen Periode sollte mit exakt denselben Finanzwerten erfolgreich durchlaufen.

Race-Condition-Test. Starten Sie einen Buchungsvorgang in einer offenen Periode, sperren Sie die Periode mitten während des Requests und bestätigen Sie, dass der Commit die Sperre nicht umgehen kann. Das kritische Erfolgskriterium: Eine festgeschriebene Buchung darf niemals in einer Periode landen, die gesperrt wurde, bevor die Transaktion abgeschlossen (committed) war.

Nachvollziehbarkeit (Auditierbarkeit) und Abstimmung (Reconciliation)

Bestätigen Sie bei jeder erfolgreichen Änderungsoperation, dass das Geschäftsobjekt existiert, der Buchungssatz existiert und Soll gleich Haben ist, der Audit-Eintrag existiert, das Webhook-Event existiert oder abrufbar ist und dass Ihr lokaler Operationsdatensatz auf die Objekt-ID des Providers verweist, wobei Retrys dieselbe logische Operation referenzieren.

Bestätigen Sie bei jeder abgelehnten Änderungsoperation, dass kein unvollständiger Beleg existiert, keine Nummer verbraucht wurde (es sei denn, der Vertrag erlaubt Reservierungen), kein Journaleintrag existiert, kein Erfolgs-Webhook versendet wurde und der Fehler eine Request-ID für Diagnosezwecke enthält. Die Konventionen von Nordlet beschreiben die Auditierbarkeit von Änderungen und ausgeglichene Buchungssätze als Plattform-Garantien, wobei der Ausgleich (Balance) nicht nur durch den Anwendungscode, sondern durch einen verzögerten Datenbank-Trigger beim Commit erzwungen wird. Machen Sie diese Garantien zu Post-Test-Reconciliation-Abfragen, anstatt nur der HTTP-Response zu vertrauen.

Kern-Testmatrix

Bereich Testaktion Erwartete Zusicherung (Assertion) Erfolgskriterium
Authentifizierung Kein Schlüssel, ungültiger Schlüssel, unzureichender Scope Strukturierte 401/403; keine Nebenwirkungen Alle abgelehnten Versuche erzeugen keinen Buchhaltungsdatensatz
Schema und Beträge Ungültige Dezimalzahl, Datum, fehlendes Pflichtfeld Validierungsfehler auf Feldebene Es erfolgt kein Retry und kein teilweiser Schreibvorgang
Normale Verbuchung Rechnung in einer offenen Periode erstellen und ausstellen Ein Dokument, ein ausgeglichenes Journal, ein Audit-Eintrag Abgefragter Status und Hauptbuch stimmen überein
Replay mit gleichem Key Identische Änderungsoperation wiederholen, gleicher Schlüssel und Body Ursprüngliches Ergebnis (Replay); kein Duplikat Objekt-ID, Nummer, Journalauswirkung und Audit bleiben singulär
Geänderter Body, gleicher Key Schlüssel mit anderem Betrag wiederverwenden Dokumentierte Ablehnung wegen Key-Wiederverwendung Originaldatensatz unverändert; nichts Neues wird geschrieben
Verbindungsabbruch Commit, Abbruch, Retry mit demselben Schlüssel Ergebnis des Replays Exakt eine buchhalterische Auswirkung
Gesperrte Periode Dokument mit Datum in einer gesperrten Periode verbuchen Ablehnung wegen Periodensperre Kein Journal, keine Nummer, kein Erfolgs-Webhook
Webhook-Signatur Falsches Secret, veränderter Body Non-2xx vom Empfänger Nur der korrekte Raw Body und das korrekte Secret werden akzeptiert

Womit ich anfangen würde

Wenn die Zeit knapp ist, führen Sie die Tests in der Reihenfolge durch, die die teuersten Bugs aufdeckt. Beginnen Sie mit dem Verbindungsabbruch-Retry, da genau dieses Szenario die meisten echten Duplikate erzeugt. Danach folgt das Idempotenz-Paar (gleicher Schlüssel und geänderter Body). Anschließend gesperrte Perioden, da eine Buchung in einem abgeschlossenen Monat ein Audit-Problem darstellt, das oft erst spät auffällt. Signaturverifizierung und Out-of-Order-Webhooks kommen als Nächstes, und die vollständige Schema- und Autorisierungsmatrix kann kontinuierlich in der CI laufen, sobald sich der Client stabilisiert hat.

Alles andere in diesem Plan unterstützt diese fünf Schwerpunkte. Bringen Sie diese gegen einen Sandbox-Mandanten ins Grüne – wobei Leseabfragen zur Abstimmung (Reconciliation) die Nebenwirkungen bestätigen – und Sie können sich sicher sein, dass die Integration sicher ist, bevor sie auch nur ein einziges echtes Hauptbuch berührt. Sie können in wenigen Minuten über Erste Schritte ein Sandbox-Unternehmen und einen Testschlüssel mit beschränktem Scope (Scoped Test Key) erstellen, und über die Funktionen-Liste prüfen, welche Module Sie benötigen.

FAQ

Reicht eine 2xx-Response aus, um zu bestätigen, dass ein buchhalterischer Schreibvorgang erfolgreich war?

Nein. Ein Statuscode bestätigt lediglich, dass der Server geantwortet hat, nicht, dass exakt ein Dokument, eine Nummernvergabe und ein ausgeglichener Buchungssatz existieren. Jeder Mutationstest sollte nach der Response das Objekt, seine buchhalterische Auswirkung und sein Audit-Protokoll auslesen. Der gefährliche Fall ist die Gegenrichtung: Eine Operation, die auf dem Server festgeschrieben (committed) wurde, während der Client die Response überhaupt nie erhalten hat.

Sollte ich einen neuen Idempotenz-Schlüssel generieren, wenn ein Request in einen Timeout läuft?

Nein, und genau dieser Fehler verursacht die meisten Duplikate in der Produktion. Der Schlüssel identifiziert die logische Geschäftsoperation, nicht den Netzwerkversuch. Wiederholen Sie den Request mit demselben Schlüssel und stimmen Sie ihn anschließend ab (Reconciliation), indem Sie die Ressource auslesen. Eine Retry-Bibliothek, die für jeden Versuch eine neue UUID generiert, macht aus einer eigentlich beabsichtigten Rechnung mehrere voneinander unabhängige Buchungen.

Was passiert, wenn ich einen Idempotenz-Schlüssel mit einer anderen Payload wiederverwende?

Eine ordnungsgemäß funktionierende API lehnt dies ab, anstatt den neuen Body stillschweigend anzuwenden. Nordlet liefert 422 idempotency_key_reuse und lässt den ursprünglichen Datensatz unangetastet. Ihr Client sollte dies als Programmierfehler behandeln, den es zu beheben gilt, und nicht als einen Zustand, den man umgeht, indem man einfach einen neuen Schlüssel generiert.

Sind Webhooks ein Ersatz für das Auslesen des erstellten Objekts?

Nein. Die Zustellung eines Webhooks beweist die nachgelagerte Benachrichtigung, nicht, dass die Transaktion so abgeschlossen wurde, wie Sie es erwarten, und die Reihenfolge der Zustellung ist nicht garantiert. Verifizieren Sie die Signatur über den rohen Body (Raw Body), deduplizieren Sie anhand der Delivery- oder Event-ID und rufen Sie den aktuellen Status von der API ab, wann immer die bloße Reihenfolge der Events nicht ausreicht, um zu entscheiden, was sich geändert hat.

Wie teste ich das Verbuchen in eine gesperrte Buchungsperiode?

Erstellen Sie identische Belege in einer offenen und in einer gesperrten Periode. Stellen Sie sicher, dass der Fall der gesperrten Periode abgelehnt wird – ohne Journalzeilen, ohne verbrauchte Belegnummer, ohne Statusübergang und ohne Erfolgs-Webhook. Führen Sie danach die Race-Condition-Variante durch: Starten Sie einen Buchungsvorgang in einer offenen Periode und sperren Sie die Periode mitten im Request. Eine festgeschriebene Buchung darf niemals in einer Periode landen, die gesperrt wurde, bevor die Transaktion abgeschlossen (committed) war.

Kann ich massenhafte Duplikat-Tests in einem Sandbox-Unternehmen ausführen?

Ja. Ein Sandbox-Unternehmen verhält sich wie ein echtes, ist jedoch als Testdatenbestand gekennzeichnet und wird direkt (nicht erst nach Ablauf der Aufbewahrungsfrist) gelöscht. Daher ist es der richtige Ort für destruktive Tests und Duplikatsprüfungen. Beachten Sie jedoch, dass die Nutzung der Sandbox genau wie die reale Nutzung abgerechnet und gemessen wird – planen Sie das Volumen also gezielt, anstatt endlos in einer Schleife zu testen.