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.
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
Przeczytaj również
Jak wybrać firmę IT do stworzenia strony lub aplikacji
Jak wybrać firmę IT: 5 pytań do wykonawcy, red flags i checklist. Na co uważać zamawiając stronę www lub aplikację webową
Od strony wizytówki do platformy SaaS - ewolucja obecności online
Od wizytówki za 1-2.5 tys. zł po platformę SaaS za 15 tys. zł - 4 etapy rozwoju obecności online. Każdy etap z budżetem i konkretnymi efektami
Vibecoding - nowa era tworzenia oprogramowania
Czym jest vibecoding, jak działa i dlaczego projekty powstają 5-10x szybciej. Nowy model pracy w IT