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 /validatePOST /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.
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.xmlZachowanie 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: truesygnalizuje ż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
409zRetry-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:
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;
}
}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.