KSeF Faktury
PL
Zapisz się na start
Wczesny dostęp

API jest we wczesnym dostępie: klucze testowe (KSeF TEST) wydajemy partiami osobom z listy startowej. Tryb produkcyjny włączymy później.

Poproś o dostęp

Przewodniki

Webhooki

Zamiast pytać co kilka sekund o status faktury, zarejestruj adres. Gdy KSeF przyjmie albo odrzuci dokument, wyślemy podpisane zdarzenie.

Typy zdarzeń

Zdarzenia powstają, gdy dokument osiąga stan końcowy.

Typ zdarzeniaKiedy
invoice.acceptedKSeF przyjął fakturę: jest numer KSeF, a PDF z kodem QR jest gotowy lub zaraz będzie.
invoice.rejectedKSeF odrzucił fakturę. Kod i opis w last_error (np. ksef_duplicate_number).
invoice.skippedZamówienia nie da się zafakturować (np. brak NIP, brak stawki VAT). Powód w last_error.
correction.acceptedKSeF przyjął fakturę korygującą.
correction.rejectedKSeF odrzucił fakturę korygującą.
correction.skippedZwrotu nie da się zamienić w korektę (np. faktura pierwotna odrzucona).

Rejestracja endpointu

Zarejestruj adres przez API. Sekret whsec_... dostaniesz tylko w tej jednej odpowiedzi: zapisz go od razu. enabled_events: ["*"] oznacza wszystkie typy. W trybie produkcyjnym adres musi być https://, w testowym wolno http://. Każdy tryb ma do 16 endpointów.

POST /v1/webhook_endpoints
curl -X POST https://app.ksefshop.pl/v1/webhook_endpoints \
  -H "Authorization: Bearer $KSEF_API_KEY" \
  -H "Idempotency-Key: webhook-shop-backend" \
  -H "Content-Type: application/json" \
  -d '{
  "url": "https://example.com/ksef/webhook",
  "enabled_events": [
    "*"
  ],
  "description": "Shop backend"
}'

Co przychodzi

POST z obiektem Event w treści i dwoma nagłówkami: KSeF-Signature: t=<unix>,v1=<hex> oraz KSeF-Event-Id. W data.object jest faktura albo korekta w stanie z chwili zdarzenia.

Odpowiedź
{
  "id": "evt_Qa1Ws2Ed3Rf4Tg5Yh6Uj7Ik8",
  "object": "event",
  "type": "invoice.accepted",
  "livemode": false,
  "created": 1760865214,
  "data": {
    "object": {
      "id": "inv_4fJ2kP9xQwErTy7uI0oPaSdF",
      "object": "invoice",
      "livemode": false,
      "status": "accepted",
      "order": {
        "external_id": "SO-1001",
        "number": "1001"
      },
      "number": "FV/2026/10/1",
      "ksef_number": "4235405606-20261019-0E7570400000-57",
      "ksef_environment": "test",
      "issue_date": "2026-10-19",
      "currency": "PLN",
      "total_gross": "163.99",
      "buyer_nip": "5260250274",
      "pdf_url": "https://app.ksefshop.pl/pdf/cmgxa1b2c0000/Qk9tZ2FsX3ZhbGlkX3Rva2VuX2V4YW1w",
      "last_error": null,
      "created": 1760865185,
      "updated": 1760865214,
      "corrections": []
    }
  }
}

Weryfikacja podpisu

v1 to HMAC-SHA256 z Twoim sekretem jako kluczem, liczony z tekstu t + "." + surowe body. Żeby zaufać zdarzeniu:

  1. Rozbierz nagłówek KSeF-Signature na t i v1.
  2. Odrzuć zdarzenie, gdy t jest starsze niż 5 minut (ochrona przed powtórzeniem).
  3. Policz HMAC z surowego body, zanim framework zamieni je w JSON. Ponowna serializacja zmienia bajty i podpis się nie zgadza.
  4. Porównaj wyniki w stałym czasie (hash_equals, timingSafeEqual, compare_digest).
Weryfikacja podpisu KSeF-Signature
# Wyślij do swojego odbiorcy testowe zdarzenie podpisane tak jak nasze
SECRET=whsec_shownOnlyOnceExampleValue0000
BODY='{"id":"evt_test","object":"event","type":"invoice.accepted","livemode":false,"created":1760865214,"data":{"object":{"id":"inv_test"}}}'
T=$(date +%s)
SIG=$(printf '%s.%s' "$T" "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.* //')
curl -X POST http://localhost:8000/ksef/webhook \
  -H "Content-Type: application/json" \
  -H "KSeF-Signature: t=$T,v1=$SIG" \
  -H "KSeF-Event-Id: evt_test" \
  -d "$BODY"

Zakładka Shell wysyła do Twojego odbiorcy testowe zdarzenie podpisane dokładnie tak jak nasze, więc weryfikację sprawdzisz lokalnie bez KSeF.

Odpowiedź i ponowienia

Każda odpowiedź 2xx oznacza dostarczenie. Odpowiadaj szybko, a cięższą pracę rób w tle. Gdy odpowiedź nie jest 2xx albo jej nie ma, ponawiamy:

Próba Po czasie
1 1 minuta
2 5 minut
3 30 minut
4 2 godziny
5 5 godzin
6 10 godzin
7 24 godziny
8 24 godziny

Po ośmiu ponowieniach (łącznie około 3 dni) przestajemy, ale zdarzenie zostaje w /v1/events.

Duplikaty i kolejność

Dostarczamy co najmniej raz i bez gwarancji kolejności. To samo zdarzenie może przyjść dwa razy, dlatego zapisuj obsłużone KSeF-Event-Id i pomijaj powtórki. Gdy potrzebujesz aktualnego stanu, pobierz dokument przez GET /v1/invoices/{id}.

Uzgadnianie przez listę zdarzeń

Jeśli Twój serwer był wyłączony, pobierz zdarzenia, których nie obsłużyłeś:

GET /v1/events
curl "https://app.ksefshop.pl/v1/events?limit=10" \
  -H "Authorization: Bearer $KSEF_API_KEY"

Testy lokalne

Nasz serwer musi dosięgnąć Twojego adresu, więc localhost nie zadziała. Do testów użyj tunelu (np. ngrok albo Cloudflare Tunnel) i zarejestruj jego adres w trybie testowym.

KSeF Faktury

Wczesny dostęp do API

Klucze testowe wydajemy partiami. Zostaw e-mail: napiszemy, gdy dostęp będzie gotowy.