Wróć do bloga
Poradniki 2026-04-08 10 min czytania

KSeF 2.0 od zera — certyfikat, token, środowiska demo/test, biblioteka n1ebieski i offline24

Praktyczny poradnik integracji z KSeF: certyfikat .p12 vs token (wygaszany), środowiska demo/test/prod, biblioteka n1ebieski, generowanie QR offline i certyfikatowego.

KSeF 2.0 od zera — certyfikat, token, środowiska demo/test, biblioteka n1ebieski i offline24

Krajowy System e-Faktur (KSeF) staje się obowiązkowy. Jeśli masz system, który generuje faktury — musisz go zintegrować. Ten poradnik opisuje krok po kroku jak to zrobić: od wyboru metody uwierzytelniania, przez konfigurację środowisk, po generowanie kodów QR offline i certyfikatowych.

Piszę z perspektywy programisty, który przeszedł przez to od zera. Każdy problem opisany tutaj to problem, na który naprawdę natrafiliśmy.

Oficjalna strona KSeF: https://www.podatki.gov.pl/ksef/ — tu znajdziesz regulacje, FAQ i harmonogram wdrożenia. Dokumentacja techniczna API: https://www.gov.pl/web/kas/krajowy-system-e-faktur

Token vs certyfikat — co wybrać? Wybierz certyfikat.

KSeF oferuje dwa sposoby uwierzytelniania: token API i certyfikat X.509. Odpowiedź jest prosta i jednoznaczna: wybierz certyfikat. Token jest wygaszany.

Token API to starsza metoda, która działała od początku KSeF. Mechanizm: generujesz token w portalu KSeF, przechowujesz go w bazie danych systemu, przy każdej sesji pobierasz klucz publiczny KSeF, szyfrujesz token algorytmem RSA-OAEP i wysyłasz jako challenge-response. Ministerstwo Finansów oficjalnie ogłosiło plan wyłączenia uwierzytelniania tokenem w 2026 roku. Budowanie nowej integracji na tokenie to inwestycja w technologię z datą ważności. Nie rób tego.

Problemy z tokenem: wymaga ręcznej implementacji szyfrowania RSA-OAEP (lub użycia niskopoziomowej biblioteki), token w bazie danych to wektor ataku (wycieka z SQLi lub backupu), brak możliwości generowania QR II (kod certyfikatowy), brak wsparcia w nowoczesnych bibliotekach — np. n1ebieski preferuje certyfikat.

Certyfikat X.509 (.p12 / PKCS#12) to nowy i docelowy standard. Plik .p12 to kontener zawierający certyfikat X.509 (klucz publiczny + dane podmiotu) oraz klucz prywatny, chronione hasłem. Biblioteka n1ebieski (https://github.com/N1ebieski/ksef-php-client) obsługuje całą kryptografię automatycznie — otwarcie sesji, generowanie klucza AES-256, szyfrowanie, wysyłka, zamknięcie sesji. Ty dostarczasz ścieżkę do .p12 i hasło — resztą zajmuje się biblioteka.

Dodatkowa korzyść certyfikatu: możesz generować kody QR II (z podpisem cyfrowym RSA-PSS), co potwierdza tożsamość wystawcy faktury i zwiększa jej wiarygodność.

Środowiska: demo, test, produkcja — czym się różnią

To jeden z najczęstszych źródeł pomyłek. KSeF ma TRZY oddzielne środowiska i każde zachowuje się inaczej. Pomylenie ich to godziny straconego debugowania.

DEMO — środowisko deweloperskie: - API: https://ksef-demo.mf.gov.pl (portal) / https://api-demo.ksef.mf.gov.pl (API) - Dokumentacja Swagger: https://api-demo.ksef.mf.gov.pl/docs/v2/index.html - Przyjmuje DOWOLNY NIP i testowe certyfikaty (nawet self-signed) - Dane są trwałe — nie resetują się, co ułatwia debugowanie - Kody QR kierują na qr-demo.ksef.mf.gov.pl - Tu budujesz i debugujesz integrację. Tu spędzisz 90% czasu.

TEST — środowisko pre-produkcyjne: - API: https://ksef-test.mf.gov.pl (portal + API) - Dokumentacja Swagger: https://api-test.ksef.mf.gov.pl/docs/v2/index.html - Wymaga certyfikatów testowych wygenerowanych w portalu ksef-test.mf.gov.pl - Dane resetowane periodycznie (MF czyści środowisko co jakiś czas) - Kody QR na qr-test.ksef.mf.gov.pl - Tu testujesz z certyfikatem, który wygenerujesz dla klienta. Walidacja jak na produkcji, ale bez skutków prawnych.

PRODUKCJA — system live: - API: https://ksef.mf.gov.pl (portal) / https://api.ksef.mf.gov.pl (API) - Dokumentacja Swagger: https://api.ksef.mf.gov.pl/docs/v2/index.html - Wymaga certyfikatów kwalifikowanych (podpis elektroniczny kupowany od Certum, Sigillum, EuroCert) - Prawdziwe faktury widoczne w systemie Ministerstwa Finansów - Kody QR na qr.ksef.mf.gov.pl - Tu NIE testujesz. Każda wysłana faktura to prawdziwy dokument podatkowy.

Kluczowa różnica: DEMO akceptuje byle co — możesz wysłać fakturę z NIP-em 1234567890 i self-signed certem. TEST wymaga prawdziwego certyfikatu testowego i poprawnego NIP-u. PRODUKCJA wymaga kwalifikowanego certyfikatu.

W kodzie najlepiej mieć tablicę URL-i indeksowaną statusem (1=demo, 2=prod, 3=test) i przełączać je jedną zmienną konfiguracyjną. Nigdy nie hardcoduj URL-i środowiska w kodzie.

Biblioteka n1ebieski/ksef-php-client — co to jest i jak jej używać

n1ebieski/ksef-php-client to open-source'owa biblioteka PHP stworzona przez polskiego programistę (GitHub: https://github.com/N1ebieski/ksef-php-client). To najdojrzalsza i najlepiej utrzymywana biblioteka PHP do KSeF API v2 jaką znaleźliśmy. Ma regularne aktualizacje, obsługuje KSeF 2.0 na branchu main (release ^1.0).

Co robi biblioteka: - Obsługuje uwierzytelnianie certyfikatem (.p12) i tokenem (legacy) - Automatycznie generuje losowy klucz AES-256-CBC do szyfrowania XML - Otwiera i zamyka sesje z KSeF API - Konwertuje tablicę PHP na poprawny XML FA(3) przez system DTO (Data Transfer Object) - Waliduje XML przed wysyłką przez wbudowaną walidację XSD - Obsługuje batch sending (wiele faktur w jednej sesji) - Polluje status wysłanej faktury i zwraca numer KSeF - Generuje PDF i UPO (Urzędowe Poświadczenie Odbioru)

Instalacja: composer require n1ebieski/ksef-php-client

Podstawowy flow wysyłki faktury:

1. Tworzymy klienta: new Client z Mode (Demo/Test/Production), ścieżką do .p12 i hasłem. Mode to enum — Mode::Demo, Mode::Production, Mode::Test. 2. Budujemy DTO faktury: Faktura::from($data) — przekazujesz tablicę asocjacyjną z danymi sprzedawcy (Podmiot1), nabywcy (Podmiot2), wierszami (FaWiersz), płatnościami (Platnosc). 3. Wywołujemy sendInvoice() — biblioteka: otwiera sesję online (POST /sessions/online), generuje losowy klucz AES-256 (EncryptionKeyFactory::makeRandom()), konwertuje DTO na XML, szyfruje XML kluczem AES, wysyła (POST /sessions/online/{ref}/invoices), zamyka sesję (POST /sessions/online/{ref}/close) 4. Pobieramy numer KSeF — Utility::retry() automatycznie polluje endpoint statusu z 2-sekundowymi przerwami (domyślnie 5 prób). Po akceptacji zwraca numer KSeF.

Kluczowe klasy: Client (główna klasa do połączenia), Faktura (DTO z walidacją typów), FakturaWiersz (wiersz faktury), EncryptionKeyFactory (generator kluczy AES), Mode (enum środowiska), Utility (retry, helpers).

Pułapki z n1ebieski:

- Wymaga rozszerzenia PHP bcmath do operacji kryptograficznych na dużych liczbach. Na shared hostingu (np. wirtualne.net, home.pl) może go nie być. Rozwiązanie: composer require phpseclib/bcmath_compat — polyfill implementujący bcmath w czystym PHP, zero zależności systemowych. - NrWierszaFa musi być int, nie string — biblioteka ściśle waliduje typy DTO. PHP domyślnie traktuje klucze tablicy jako stringi — rzutuj explicite: (int)$nrWiersza. - Tablica wierszy faktury (FaWiersz) musi być indeksowana od 1, nie od 0. Użyj array_combine(range(1, count($items)), $items). - Mode enum mapuje się na środowiska automatycznie — nie musisz ręcznie budować URL-i.

Schemat FA(3) — struktura faktury XML

FA(3) to trzecia wersja schematu faktury elektronicznej w KSeF. Oficjalny schemat XSD jest opublikowany w Centralnym Repozytorium Dokumentów pod adresem: https://crd.gov.pl/wzor/2024/10/16/13271/ — stamtąd pobierzesz plik XSD do walidacji lokalnej.

FA(3) to ponad 200 pól zorganizowanych w ściśle hierarchiczną strukturę XML. Najważniejsze sekcje:

- Naglowek: data wystawienia, numer faktury, waluta, typ (FA/KOR/ZAL/ROZ) - Podmiot1 (sprzedawca): DaneIdentyfikacyjne (NIP, nazwa, PrefiksPodatnika), Adres - Podmiot2 (nabywca): DaneIdentyfikacyjne, Adres (opcjonalny) - Fa: główna sekcja z wierszami (FaWiersz), podsumowaniami VAT, płatnościami - FaWiersz: P_7 (nazwa), P_8A (jednostka), P_8B (ilość), P_9A (cena netto), P_11 (wartość netto), P_12 (stawka VAT) - Platnosc: Zaplacono, DataZaplaty, FormaPlatnosci, TerminPlatnosci, RachunekBankowy

Krytyczne zasady walidacji XSD: - Kolejność elementów MUSI odpowiadać schematowi. Np. w sekcji Platnosc: Zaplacono → DataZaplaty → FormaPlatnosci → RachunekBankowy. Inna kolejność = odrzucenie. - Pola Zaplacono i TerminPlatnosci są wzajemnie wykluczające. Opłacona faktura nie ma terminu płatności. - PrefiksPodatnika musi być WEWNĄTRZ DaneIdentyfikacyjne, nie obok. - NrWierszaFa to integer, nie string. - Indeksowanie wierszy od 1.

Narzędzie: pobierz XSD z CRD, waliduj lokalnie (xmllint --schema fa3.xsd faktura.xml) przed każdą wysyłką na KSeF. Zaoszczędzisz godziny debugowania enigmatycznych komunikatów.

Generowanie certyfikatu — kwalifikowany vs testowy

Są dwa rodzaje certyfikatów i nie wolno ich mylić:

Certyfikat kwalifikowany (produkcja) — kupujesz od dostawcy podpisu elektronicznego (Certum, Sigillum, EuroCert, Asseco). To fizyczna karta kryptograficzna lub token USB z certyfikatem i kluczem prywatnym. Kosztuje 200-400 zł/rok. Wymagany na środowisku produkcyjnym KSeF. Musisz wyeksportować certyfikat do formatu .p12 (PKCS#12).

Certyfikat testowy (demo/test) — generujesz sam w portalu KSeF test (https://ksef-test.mf.gov.pl). Bezpłatny. Używany wyłącznie do testów. Na środowisku DEMO akceptowane są nawet certyfikaty self-signed (openssl req -x509 -newkey rsa:2048).

Format .p12 (PKCS#12): to kontener binarny zawierający certyfikat X.509 (klucz publiczny + dane podmiotu + łańcuch CA) oraz klucz prywatny, zaszyfrowane hasłem. Tworzysz go z plików .crt i .key komendą: openssl pkcs12 -export -in cert.crt -inkey private.key -out certyfikat.p12 -passout pass:TwojeHaslo. Hasło ustalone przy eksporcie jest wymagane w kodzie do otwarcia pliku.

Przechowywanie na serwerze: plik .p12 MUSI być poza katalogiem webroot (np. ~/private/ksef/, NIE ~/public_html/). Uprawnienia: chmod 600 (tylko właściciel odczyt+zapis). Hasło przechowuj w bazie danych (zakodowane base64), nie w kodzie źródłowym ani w plikach konfiguracyjnych commitowanych do repozytorium.

QR kody: offline (QR I) vs certyfikatowy (QR II) — kluczowa różnica

KSeF wymaga kodów QR na fakturze PDF. Są dwa typy i używają RÓŻNYCH certyfikatów — to najczęstszy błąd implementacji.

QR I — offline (hashowy): URL zawierający NIP wystawcy, datę wystawienia i hash SHA-256 treści XML faktury. Pozwala odbiorcy zweryfikować autentyczność faktury bez połączenia z internetem — wystarczy porównać hash XML z hashem w URL. Nie wymaga żadnego certyfikatu offline — tylko obliczenia SHA-256.

Format: https://qr-{env}.ksef.mf.gov.pl/invoice/{NIP}/{DD-MM-RRRR}/{SHA256_HEX} Generowanie: 1) Oblicz SHA-256 z pełnego XML faktury 2) Złóż URL z NIP, datą (DD-MM-RRRR) i hexem SHA-256 3) Zakoduj URL jako kod QR na PDF.

QR II — certyfikatowy (podpisany RSA-PSS): URL z podpisem cyfrowym wykonanym kluczem prywatnym z certyfikatu OFFLINE. Potwierdza tożsamość wystawcy — nie tylko autentyczność treści. To jak pieczęć notarialna na dokumencie.

UWAGA KRYTYCZNA: QR II używa INNEGO certyfikatu niż certyfikat do uwierzytelniania z API KSeF. Certyfikat auth (.p12 do logowania) to certyfikat z atrybutem Digital Signature. Certyfikat offline (do QR II) to certyfikat z atrybutem Non-Repudiation — typ ECDSA lub RSA, generowany oddzielnie.

Najczęstszy błąd (naprawiliśmy go w naszej integracji): użycie numeru seryjnego certyfikatu auth w parametrze certyfikatId kodu QR II. To dwa RÓŻNE certyfikaty z RÓŻNYMI numerami seryjnymi. Numer seryjny certyfikatu offline wyciągasz z pola offline_cert w konfiguracji, NIE z auth_cert.

Algorytm podpisu QR II: RSA-PSS z SHA-256. Starsze implementacje używały PKCS#1 v1.5 (OPENSSL_ALGO_SHA256) — to błąd. QR II wymaga RSA-PSS (OPENSSL_ALGO_SHA256 z opcją padding=OPENSSL_PKCS1_PSS_PADDING w openssl_sign). Dla certyfikatów ECDSA: dynamiczne paddowanie komponentów (nie hardcoded 32 bajty).

Generowanie QR II wymaga: typKontekstu, kontekstId (numer sesji lub faktury), NIP, certyfikatId (serial z cert offline), hash XML (SHA-256), podpis RSA-PSS zakodowany base64url.

Problemy po drodze — z czym się zmierzysz

XSD walidacja: Schemat FA(3) jest bezlitosny. Kolejność elementów XML musi DOKŁADNIE odpowiadać schematowi. Dodanie opcjonalnego pola w złym miejscu = odrzucenie. Pobierz schemat XSD z https://crd.gov.pl/wzor/2024/10/16/13271/ i waliduj KAŻDY XML lokalnie przed wysyłką.

Zamykanie sesji: KSeF wymaga POST /sessions/online/{ref}/close. Nie DELETE, nie GET, nie PATCH. Tylko POST. Inne metody zwracają 405 bez wyjaśnienia.

Zaplacono vs TerminPlatnosci: te pola są wzajemnie wykluczające i walidacja XSD jest tu bezwzględna. Jeśli Zaplacono=1, nie podajesz TerminPlatnosci ani DataZaplaty w formacie przyszłym. Jeśli nieopłacona — TerminPlatnosci jest wymagany, DataZaplaty zakazany. Jedna literówka i faktura odrzucona.

Bcmath na shared hosting: jeśli phpinfo() nie pokazuje bcmath — NIE próbuj instalować rozszerzenia na shared hostingu. Użyj polyfill: composer require phpseclib/bcmath_compat. Działa identycznie, jest w czystym PHP.

Blokada edycji po wysyłce: po wysłaniu faktury do KSeF (status=2) dane nabywcy nie mogą się zmienić — faktura jest już dokumentem prawnym w systemie MF. Implementuj blokadę formularzy (readonly/disabled), ukryj przycisk usuwania, pokaż ostrzeżenie "Faktura wysłana do KSeF — edycja zablokowana".

Specyfikacja API: pełna dokumentacja OpenAPI (Swagger) jest dostępna dla każdego środowiska. Pliki YAML: https://ksef-demo.mf.gov.pl/openapi/gtw/svc/api/KSeF-online.yaml (API online), KSeF-batch.yaml (batch), KSeF-common.yaml (wspólne schematy). Interaktywna dokumentacja pod /docs/v2/index.html na każdym środowisku.

Architektura integracji

Najprostsza architektura: tabela konfiguracji KSeF (NIP, status środowiska 1/2/3, ścieżka certyfikatu auth, hasło, cert offline, serial offline) + kolumny ksef_* w tabeli faktur (ksef_xml MEDIUMTEXT, ksef_nr VARCHAR, ksef_ref VARCHAR, ksef_status INT, ksef_sent_at DATETIME, ksef_qr1 TEXT, ksef_qr2 TEXT). Statusy: 0=nowa, 1=oczekuje, 2=wysłana/zaakceptowana, 3=błąd.

Flow: rezerwacja/zamówienie → generuj XML FA(3) → zapisz w ksef_xml → użytkownik klika "wyślij" → n1ebieski otwiera sesję → szyfruje AES-256 → wysyła → zamyka → Utility::retry() polluje status → numer KSeF wraca → update bazy → generuj QR na PDF.

Batch: wysyłaj sekwencyjnie (jedna po drugiej w pętli), nie równolegle. KSeF ma rate limiting i sesje nie obsługują concurrent access. Jedna sesja → jedna faktura → zamknij → następna.

Podsumowanie: checklista integracji

1. Zainstaluj n1ebieski (https://github.com/N1ebieski/ksef-php-client): composer require n1ebieski/ksef-php-client (+ phpseclib/bcmath_compat jeśli brak bcmath) 2. Wygeneruj certyfikat testowy na https://ksef-test.mf.gov.pl 3. Stwórz .p12 z openssl i umieść POZA webroot (chmod 600) 4. Napisz generator XML FA(3) — pobierz schemat XSD z https://crd.gov.pl/wzor/2024/10/16/13271/ i waliduj lokalnie 5. Zaimplementuj wysyłkę przez n1ebieski (Client + Faktura::from() + sendInvoice + retry) 6. Dodaj QR I (SHA-256 hash) i opcjonalnie QR II (certyfikat offline z osobnym certem Non-Repudiation) 7. Przetestuj na DEMO (https://ksef-demo.mf.gov.pl) → potem TEST (https://ksef-test.mf.gov.pl) → dopiero potem PRODUKCJA 8. Zablokuj edycję faktur po wysłaniu (ksef_status=2) 9. Przeczytaj Swagger API: https://api-demo.ksef.mf.gov.pl/docs/v2/index.html

Cała integracja dla doświadczonego programisty PHP to 20-30 godzin pracy. Dla kogoś kto robi to pierwszy raz — 40-60 godzin, głównie z powodu debugowania XSD i zrozumienia różnic między certyfikatami.

Potrzebujesz pomocy z integracją KSeF? Napisz na michal@mikamait.pl — mamy to już za sobą i wiemy dokładnie gdzie są pułapki.

Udostępnij