Nordlet

← Dokumentation / Ordlista

Vad är idempotens? Därför behöver bokförings- och betalnings-API:er det

Hur ett förnyat anrop skapar en dubblettfaktura, vad en idempotensnyckel garanterar, vilka felscenarier den måste hantera och hur du implementerar den korrekt.

En åtgärd är idempotent om den ger samma resultat oavsett om den utförs en eller flera gånger. Att läsa en post är naturligt idempotent. Att skapa en faktura är det inte - gör det två gånger och du har två fakturor, två kundfordringar och en kund som undrar varför de har debiterats två gånger.

Den skillnaden spelar roll eftersom nätverk brister på ett specifikt och besvärligt sätt: anropet lyckas, men svaret går förlorat. Klienten ser en tidsgränsöverskridning (timeout) och kan inte skilja på "kom aldrig fram" och "kom fram, fungerade, svar förlorat". Det enda säkra alternativet är att göra om anropet, och det är just detta försök som skapar dubbletten.

Varför bokföring gör det värre

I de flesta system är en dubblett ett irritationsmoment. I en huvudbok är den ett växande problem:

  • En dubblettfaktura förbrukar ett nummer från en luckfri nummerserie (gapless sequence), så att radera den är inte ett alternativ - oföränderlighet innebär att en rättelse kräver en kreditfaktura, vilket är ett dokument som din kund får se.
  • Dubbletten bokför verifikat, så intäkter, moms och kundfordringar är överskattade tills någon upptäcker det.
  • Den hamnar i momsdeklarationen och, i Litauen, i i.SAF-registren.
  • Om den utlöste en betalning har pengar faktiskt flyttats.

"Det är bara att leta efter dubbletter" fungerar inte heller, eftersom två helt skilda fakturor till samma kund på samma belopp samma dag är fullt normala. Systemet kan inte härleda avsikten enbart från innehållet - klienten måste ange den.

Hur idempotensnycklar fungerar

Klienten genererar en unik nyckel per logisk åtgärd och skickar med den i anropet:

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

Servern garanterar då följande:

  1. Första anropet med denna nyckel - bearbeta som vanligt, spara resultatet kopplat till nyckeln.
  2. Nytt försök med samma nyckel och samma payload - kör inte om; returnera det sparade svaret.
  3. Samma nyckel, annan payload - avvisa. Nyckeln är en försäkran om att detta är samma åtgärd; en annan kropp innebär att den inte är det, och att i tysthet returnera det gamla resultatet vore värre än ett fel.
  4. Nytt försök medan det ursprungliga fortfarande körs - avvisa med ett fel som går att göra om (retryable) i stället för att köra samtidigt.

Punkt 4 är den som naiva implementationer missar. En klient som får en timeout efter 5 sekunder och försöker igen efter 6 kommer att krocka med sin egen pågående begäran. Utan ett lås för samtidighet (concurrency guard) går båda igenom och du får den dubblett som mekanismen skapades för att förhindra.

Att generera nycklar korrekt

Nyckeln måste identifiera åtgärden, inte själva försökstillfället:

  • Rätt: en UUID som genereras en gång när användaren klickar på "utfärda faktura" och som återanvänds för varje nytt försök av det klicket.
  • Rätt: en deterministisk nyckel som härleds från din egen domän - order-1041-invoice.
  • Fel: en helt ny UUID per HTTP-försök. Varje nytt försök får en ny nyckel, vilket innebär att varje försök betraktas som en ny åtgärd och du har inte implementerat någonting alls.
  • Fel: en hash av payloaden. Två legitimt identiska åtgärder krockar då, och den andra sväljs i tysthet.

Nycklar behöver en lagringstid (retention period) - tillräckligt lång för att täcka realistiska tidsfönster för nya försök och klientavbrott, men inte för evigt.

Vad den inte löser

Idempotens skyddar mot dubbbelkörning av samma anrop. Den skyddar inte mot:

  • Två olika användare som skapar samma faktura från olika sessioner - det är ett affärsregelproblem som löses med unika begränsningar (uniqueness constraints), till exempel en faktura per order.
  • Logiska dubbletter med olika nycklar.
  • Icke-determinisim i åtgärden - om hanteraren genererar en tidsstämpel eller ett nummer fångar det sparade svaret det första, vilket är önskvärt beteende men värt att förstå.

Den kompletterar snarare än ersätter databasbegränsningar. Nordlets huvudbok använder båda: idempotensnycklar stoppar upprepade anrop, medan unika index stoppar dubbla utbetalningsbatchar per utbetalnings-ID, dubbla leverantörsfakturor per leverantör och dokumentnummer, samt dubbla avskrivningskörningar per anläggningstillgång och period.

Hur Nordlet implementerar det

Skicka en Idempotency-Key-rubrik (header) vid alla anrop som ändrar tillstånd - en icke-tom sträng på som mest 255 tecken.

Det första anropet med en given nyckel utförs och dess resultat sparas. Ett nytt försök med samma nyckel och samma payload returnerar det sparade svaret utan att köras om, så fakturan skapas exakt en gång oavsett hur många gånger anropet upprepas.

Två felscenarier är explicita i stället för tysta:

  • Samma nyckel, annan payload → avvisas: "Idempotency-Key was already used with a different request payload." Detta fångar upp det genuint farliga läget där en klient återanvänder en nyckel men ändrar beloppet.
  • Samma nyckel, originalet pågår fortfarande → avvisas som tillfälligt fel: "A request with this Idempotency-Key is still being processed, retry shortly." Ingen samtidig dubbelkörning.

Nycklar är begränsade per företag, så två klienter (tenants) som genererar samma UUID krockar aldrig.

Den kompletterande garantin fungerar åt andra hållet. Webhook-leveranser sker enligt principen at-least-once, vilket innebär att din slutpunkt (endpoint) ibland kommer att ta emot samma händelse två gånger - efter en timeout där leveransen faktiskt lyckades, eller vid en omleverans. Varje händelse bär på ett ID, och din hanterare bör registrera bearbetade ID:n och ignorera upprepningar. Idempotens är en egenskap som båda sidor av en integration behöver; guiden om API-konventioner behandlar anropssidan i detalj.

FAQ

Vad är en idempotensnyckel?

En klientgenererad identifierare som skickas med ett anrop och låter servern känna igen ett upprepat försök av samma logiska åtgärd. Det första anropet utförs; nya försök med samma nyckel returnerar det sparade resultatet i stället för att köras igen.

Varför behöver bokförings-API:er idempotens?

Eftersom en dubblerad skrivning skapar ett riktigt dokument med verkliga konsekvenser - en andra faktura som förbrukar ett löpnummer, bokförs i huvudboken och hamnar i en momsdeklaration. Till skillnad från en dubblerad läsning kan den inte bara raderas; den måste rättas med en kreditfaktura.

Ska jag generera en ny nyckel för varje nytt försök?

Nej - det motverkar hela mekanismen. Generera en nyckel per logisk åtgärd och återanvänd den för varje nytt försök av den åtgärden. En ny nyckel per försök innebär att varje försök behandlas som en ny åtgärd.

Vad händer om jag återanvänder en nyckel med en annan payload?

Anropet avvisas. Nyckeln intygar att detta är samma åtgärd, så en ändrad kropp betyder att något är fel - mest troligt en bugg med återanvändning av nycklar hos klienten. Att tyst returnera det tidigare resultatet vore värre.

Förhindrar idempotens alla dubbletter?

Nej. Den förhindrar dubbelkörning av samma anrop. Två olika användare som oberoende av varandra skapar samma faktura har gjort två olika anrop, vilket är ett affärsregelproblem för unika begränsningar och inget som en idempotensnyckel kan upptäcka.