Hopp til innhold
↑ ↓ navigerEnter åpneEsc lukk
Teknisk

Idempotens: hvorfor hver POST trenger en nøkkel

En kunde sendte 1 100 fakturaer to ganger. Ingen av dem nådde mottakeren dobbelt. Her er hvorfor, og hvorfor vi mener Idempotency-Key bør være standard i alle API-er.

Marte Solheim · Teknisk direktør og medgründer 6 min lesing

En torsdag i juni kjørte en kunde en nattlig fakturajobb. Jobben opprettet 1 100 fakturaer og sendte dem. Halvveis mistet serveren deres nettverket i 40 sekunder. Jobben hadde gjenforsøk, så den startet på nytt fra begynnelsen.

Resultatet: 1 100 kall til POST /v1/fakturaer ble sendt to ganger. Antall fakturaer som nådde kundene deres: 1 100. Ikke 2 200.

Hva som skjedde

Kunden brukte Node-SDK-et og hadde satt idempotencyKey til ordre-ID-en på hvert kall. Da jobben kjørte andre gang, så Spire nøkler den hadde sett før, med samme kropp, og returnerte det lagrede svaret med Idempotent-Replayed: true. Ingen nye fakturaer, ingen nye fakturanumre, ingen nye sendinger.

Kunden merket det først dagen etter, da de så hodet i loggen. De sendte oss en e-post for å takke. Det er den beste typen support-henvendelse.

Hvorfor det ikke er selvsagt

Mange API-er behandler hvert POST-kall som nytt. Da er gjenforsøk farlig: et kall som timet ut kan ha gått gjennom, og å prøve igjen lager et duplikat. Utviklere løser det med å ikke gjenforsøke, og da mister de i stedet kall når nettverket svikter. Eller de bygger egne sjekker («finnes det en faktura for denne ordren?»), som har sine egne kappløp.

Idempotency-Key flytter problemet til serveren, der det hører hjemme. Serveren vet om kallet gikk gjennom. Klienten trenger bare å gjenta med samme nøkkel.

Hvordan vi implementerer det

Nøkkelen og en hash av kroppen lagres i en tabell med (konto, miljø, nøkkel) som primærnøkkel, sammen med svaret, i 24 timer. Flyten:

  1. Kall kommer inn med nøkkel. Vi prøver å sette inn en rad med status «pågår».
  2. Lyktes innsettingen, er dette første gang. Vi utfører kallet, lagrer svaret på raden, og returnerer det.
  3. Feilet innsettingen fordi raden finnes, sjekker vi hashen. Lik hash: returner lagret svar (eller vent på at «pågår» blir ferdig, opptil 30 sekunder). Ulik hash: 409 konflikt.

Det er alt. Det vanskelige er ikke logikken, men å gjøre den atomisk under samtidighet, og det løser databasens unike indeks for oss.

Hva med GET?

GET er idempotent av natur. DELETE er det i praksis: å slette noe som er slettet er ikke en feil hos oss. PATCH er det ofte ikke (tenk «øk antall med 1»), men i Spire er PATCH alltid «sett disse feltene til disse verdiene», som er idempotent.

POST er det eneste verbet der det må bygges inn, og det er der det betyr mest, fordi POST lager ting som koster penger: fakturaer, signeringer, SMS-er.

Hva du bør gjøre

Bruk nøkler som er knyttet til handlingen hos deg, ikke til kallet:

  • ordre-10422 for å fakturere ordre 10422
  • leie-8812-signering for å sende leieavtale 8812 til signering
  • otp-bruker-4471-2026-11-03T10:02 for en engangskode

SDK-ene setter en tilfeldig nøkkel hvis du ikke gjør det, og det beskytter mot nettverksfeil innenfor ett kall. Men det beskytter ikke mot at din jobb kjører to ganger. For det må nøkkelen komme fra deg.

Hele mekanismen er beskrevet i dokumentasjonen. Og hvis du bygger et API selv: legg det inn fra dag én. Det er en ettermiddags arbeid, og det sparer kundene dine for ettermiddager de aldri får vite om.

Første kall på under fem minutter

Opprett en gratis konto, hent en testnøkkel og gjør ditt første oppslag i sandkassen. Ingen kredittkort, ingen binding.