Developers/Idempotency

Idempotency

Bezpieczne ponawianie żądań - ten sam klucz zwraca tę samą odpowiedź przez 24h.

Po co

Sieć, serwery i Twój kod się psują. Klient wysyła POST, łapie timeout, ponawia, a serwer w międzyczasie zdążył przetworzyć oryginalne żądanie i wysłać webhook. Bez idempotency dostajesz dwie historie walidacji i dwa webhooki za ten sam plik.

Nagłówek Idempotency-Key rozwiązuje to: serwer zapamiętuje klucz + skrót body przez 24 h. Drugi request z tym samym kluczem dostaje dokładnie tę samą odpowiedź (status, body, headers) - bez ponownego przetwarzania, bez nowego webhooka.

Gdzie obowiązuje

Obecnie idempotency wspierają:

  • POST /validate
  • POST /correction/analyze

Endpointy webhookowe (CRUD na /webhooks) nie używają idempotency, bo każdy create generuje nowy sekret - replay byłby mylący.

Format klucza

Dowolny string 8-200 znaków. Zalecamy UUID v4 albo własny stabilny identyfikator z systemu źródłowego (np. invoice-42-v3). Dwa SDK (TypeScript i PHP) wstrzykują UUID automatycznie, jeśli sam nie podasz.

bash
curl https://api.naprawksef.pl/api/v1/validate \
  -X POST \
  -H "Authorization: Bearer nk_test_..." \
  -H "Content-Type: application/xml" \
  -H "Idempotency-Key: invoice-42-v3" \
  --data-binary @faktura.xml

Zachowanie serwera

  • Pierwsze żądanie - przetwarzane normalnie. Pełna odpowiedź wraz z meta zostaje zapisana z kluczem + skrótem body.
  • Replay (ten sam klucz + to samo body) - serwer zwraca zapisaną odpowiedź. Nagłówek Idempotent-Replayed: true sygnalizuje że to nie była świeża operacja.
  • Konflikt (ten sam klucz, inne body) - HTTP 409 IDEMPOTENCY_CONFLICT. Wybierz nowy klucz, bo poprzedni jest zaalokowany do innych danych.
  • Race (ten sam klucz w trakcie poprzedniego) - HTTP 409 z Retry-After. Daj poprzedniemu zakończyć się i spróbuj ponownie.

Wzorzec klienta

Dobry retry loop generuje klucz raz, na początku logicznej operacji, i używa go we wszystkich kolejnych próbach:

typescript
const idempotencyKey = `invoice-${invoice.id}-v${invoice.revision}`;

for (let attempt = 0; attempt < 3; attempt++) {
  try {
    return await client.validate.run({ xml, idempotencyKey });
  } catch (e) {
    if (e instanceof NaprawKsefIdempotencyConflictError) {
      await sleep((e.retryAfterSeconds ?? 5) * 1000);
      continue;
    }
    throw e;
  }
}
SDK robi to za Ciebie
Nasze SDK same generują UUID v4 jeśli nie podasz klucza i implementują retry z exponential backoff dla 408/425/429/5xx oraz błędów sieci.

Czas życia

Każdy klucz żyje 24 h od pierwszego użycia, a potem jest usuwany przez cron idempotency-cleanup. Jeśli musisz mieć gwarancję replay dłużej, użyj stabilniejszego klucza biznesowego (np. invoice id + revision) i tłumacz konflikty po stronie aplikacji.