Nordlet

← Documentazione / Glossario

Che cos'è l'idempotenza? Perché le API contabili e di pagamento ne hanno bisogno

Come una richiesta ripetuta crea una fattura duplicata, cosa garantisce una chiave di idempotenza, le modalità di errore che deve gestire e come implementarla correttamente.

Un'operazione è idempotente se eseguita due volte produce lo stesso effetto che eseguirla una sola volta. La lettura di un record è naturalmente idempotente. La creazione di una fattura non lo è: se la si chiama due volte si ottengono due fatture, due crediti commerciali e un cliente che chiede perché gli sia stato fatturato due volte.

Questo divario è importante perché le reti falliscono in un modo specifico e fastidioso: la richiesta va a buon fine e la risposta viene persa. Il client rileva un timeout e non è in grado di distinguere se la richiesta "non è mai arrivata" oppure se è "arrivata, è stata elaborata e la risposta è andata perduta". La sua unica opzione apparentemente sicura è riprovare, ed è proprio il nuovo tentativo che crea il duplicato.

Perché la contabilità peggiora la situazione

Nella maggior parte dei sistemi un duplicato è solo un fastidio. In un mastro contabile (ledger) è un problema che si amplifica:

  • Una fattura duplicata consuma un numero da una sequenza continua (gapless sequence), quindi eliminarla non è un'opzione: l'immutabilità significa che correggerla richiede una nota di credito, che è un documento visibile al cliente.
  • Il duplicato registra scritture contabili, per cui ricavi, IVA e crediti commerciali risultano tutti sovrastimati finché qualcuno non se ne accorge.
  • Entra nella dichiarazione IVA e, in Lituania, nei registri i.SAF.
  • Se ha attivato un pagamento, il denaro si è effettivamente mosso.

Anche l'approccio "basta controllare i duplicati" non funziona, perché due fatture genuinamente distinte per lo stesso cliente, dello stesso importo e nello stesso giorno sono perfettamente normali. Il sistema non può dedurre l'intento dal contenuto: il client deve esplicitarlo.

Come funzionano le chiavi di idempotenza

Il client genera una chiave univoca per ogni operazione logica e la invia con la richiesta:

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

Il server garantisce quindi che:

  1. Prima richiesta con questa chiave - viene elaborata normalmente e il risultato viene memorizzato in associazione alla chiave.
  2. Nuovo tentativo con la stessa chiave e lo stesso payload - non viene rieseguita; viene restituita la risposta memorizzata.
  3. Stessa chiave, payload diverso - viene rifiutata. La chiave è una dichiarazione che si tratta della stessa operazione; un corpo differente significa che non lo è, e restituire silenziosamente il vecchio risultato sarebbe peggio di un errore.
  4. Nuovo tentativo mentre l'originale è ancora in esecuzione - viene rifiutata con un errore ripetibile (retryable) anziché essere eseguita in concorrenza.

Il punto 4 è quello che le implementazioni ingenue tralasciano. Un client che va in timeout dopo 5 secondi e riprova dopo 6 entrerà in collisione con la propria richiesta ancora in corso (in-flight); senza un controllo di concorrenza, entrambe procedono e si ottiene il duplicato che il meccanismo serviva a prevenire.

Generare le chiavi correttamente

La chiave deve identificare l'operazione, non il tentativo:

  • Corretto: un UUID generato una sola volta quando l'utente fa clic su "Emetti fattura", riutilizzato per ogni nuovo tentativo di quel clic.
  • Corretto: una chiave deterministica derivata dal proprio dominio - order-1041-invoice.
  • Errato: un nuovo UUID per ogni tentativo HTTP. Ogni nuovo tentativo ottiene una chiave nuova, quindi ogni tentativo è una nuova operazione e non avete implementato nulla.
  • Errato: un hash del payload. Due operazioni legittimamente identiche finirebbero per andare in collisione e la seconda verrebbe inghiottita silenziosamente.

Le chiavi richiedono un periodo di conservazione sufficientemente lungo da coprire finestre di tentativi realistiche e interruzioni del client, ma non eterno.

Cosa non risolve

L'idempotenza protegge contro la doppia esecuzione della stessa richiesta. Non protegge contro:

  • Due utenti diversi che creano la stessa fattura da sessioni differenti: questo è un problema di regole di business, risolto da vincoli di unicità come una sola fattura per ordine.
  • Duplicati logici con chiavi differenti.
  • Mancanza di determinismo all'interno dell'operazione: se il gestore (handler) genera un timestamp o un numero, la risposta memorizzata cattura il primo, che è il comportamento desiderato ma che vale la pena comprendere.

Si affianca ai vincoli del database, anziché sostituirli. Il mastro (ledger) di Nordlet utilizza entrambi: le chiavi di idempotenza bloccano le richieste ripetute, mentre gli indici univoci bloccano i batch di liquidazione doppi per ID di pagamento, le fatture di acquisto doppie per fornitore e numero di documento, e le esecuzioni di ammortamento doppie per cespite e periodo.

Come lo implementa Nordlet

Inviate un header Idempotency-Key in qualsiasi richiesta che modifica lo stato: una stringa non vuota di massimo 255 caratteri.

La prima richiesta con una determinata chiave viene eseguita e il suo risultato viene memorizzato. Un nuovo tentativo con la stessa chiave e lo stesso payload restituisce la risposta memorizzata senza rieseguire l'operazione, per cui la fattura viene creata esattamente una volta, indipendentemente da quante volte la chiamata viene ripetuta.

Due modalità di errore sono esplicite anziché silenziose:

  • Stessa chiave, payload diverso → rifiutata: "Idempotency-Key was already used with a different request payload." Questo rileva il caso genuinamente pericoloso in cui un client riutilizza una chiave modificando l'importo.
  • Stessa chiave, richiesta originale ancora in corso → rifiutata come ripetibile: "A request with this Idempotency-Key is still being processed, retry shortly." Nessuna doppia esecuzione concorrente.

Le chiavi sono delimitate per azienda (scoped), quindi due tenant che generano lo stesso UUID non entreranno mai in collisione.

La garanzia complementare opera nella direzione opposta. La consegna dei webhook è di tipo at-least-once, il che significa che il vostro endpoint riceverà occasionalmente lo stesso evento due volte: dopo un timeout in cui la consegna è effettivamente andata a buon fine, o in caso di riconsegna. Ogni evento porta con sé un ID e il vostro gestore dovrebbe registrare gli ID elaborati e ignorare le ripetizioni. L'idempotenza è una proprietà di cui entrambi i lati di un'integrazione hanno bisogno; la guida alle convenzioni delle API copre in dettaglio il lato delle richieste.

FAQ

Che cos'è una chiave di idempotenza?

Un identificatore generato dal client inviato con una richiesta che consente al server di riconoscere un nuovo tentativo della stessa operazione logica. La prima richiesta viene eseguita; i tentativi successivi con la stessa chiave restituiscono il risultato memorizzato anziché essere eseguiti di nuovo.

Perché le API contabili hanno bisogno dell'idempotenza?

Perché una scrittura duplicata crea un documento reale con conseguenze reali: una seconda fattura che consuma un numero sequenziale, viene registrata nel mastro ed entra in una dichiarazione IVA. A differenza di una lettura duplicata, non può essere semplicemente eliminata, ma deve essere corretta con una nota di credito.

Devo generare una nuova chiave per ogni nuovo tentativo?

No, questo vanifica completamente il meccanismo. Generate una chiave per ogni operazione logica e riutilizzatela per ogni nuovo tentativo di quell'operazione. Una nuova chiave per tentativo significa che ogni tentativo viene trattato come una nuova operazione.

Cosa succede se riutilizzo una chiave con un payload diverso?

La richiesta viene rifiutata. La chiave afferma che si tratta della stessa operazione, quindi un corpo modificato significa che c'è qualcosa che non va, molto probabilmente un bug di riutilizzo della chiave sul client. Restituire il risultato precedente in modo silenzioso sarebbe peggio.

L'idempotenza previene tutti i duplicati?

No. Previene l'esecuzione duplicata della stessa richiesta. Due utenti differenti che creano indipendentemente la stessa fattura hanno effettuato due richieste diverse, il che è un problema di regole di business da gestire con vincoli di unicità, non qualcosa che una chiave di idempotenza può rilevare.