KSeF od środka: dwa serwisy, którymi rozmawiamy z systemem e-faktur

Krajowy System e-Faktur wymienia faktury jako XML i wymaga kryptograficznego logowania. Zbudowaliśmy dwa małe serwisy, które załatwiają obie te sprawy za nasze automatyzacje: ksef-crypto-worker otwiera drzwi do KSeF, a ksef-pdf-api zamienia surowy XML w fakturę, którą da się przeczytać. Ten artykuł tłumaczy od zera, co zbudowaliśmy i po co. Szczegóły techniczne opisałem w dwóch podartykułach.

KSeF w dwóch akapitach

KSeF (Krajowy System e-Faktur) to rządowa platforma, przez którą firmy w Polsce wystawiają i odbierają faktury ustrukturyzowane. Faktura w KSeF nie jest plikiem PDF ani skanem, tylko dokumentem XML zgodnym ze schematem Ministerstwa Finansów (obecnie FA(3)). Każda wystawiona faktura dostaje od systemu unikalny numer KSeF i od tego momentu jest oficjalnym dokumentem księgowym, niezależnie od tego, czy ktokolwiek ją kiedykolwiek wydrukuje.

Dla ludzi to zmiana kosmetyczna, dla systemów zasadnicza. Z KSeF nie rozmawia się przez formularz na stronie, tylko przez API: program musi się uwierzytelnić, pobrać lub wysłać XML i coś z nim dalej zrobić. I tu zaczynają się dwa konkretne problemy, na które odpowiadają nasze serwisy.

Problem pierwszy: jak się w ogóle zalogować

Do tej pory automaty logowały się do KSeF tokenem, czyli długim ciągiem znaków, który wklejało się do konfiguracji jak hasło. Ministerstwo Finansów ogłosiło, że od 1 stycznia 2027 tokeny statyczne przestają działać. Zamiast nich maszyna musi się wylegitymować certyfikatem: zbudować specjalny dokument XML, podpisać go kluczem prywatnym w formacie XAdES i wymienić ten podpis na krótkożyciowe tokeny dostępu.

Nasze automatyzacje działają w n8n, a n8n tej ceremonii kryptograficznej po prostu nie udźwignie. Kanonikalizacja XML, podpisy elektroniczne i obsługa certyfikatów to nie jest coś, co skleja się z klocków w narzędziu do przepływów. Dlatego powstał ksef-crypto-worker: mały serwis w .NET, który stoi obok n8n w tej samej prywatnej sieci i robi jedną rzecz: loguje się do KSeF certyfikatem i wydaje n8n gotowy token dostępu. n8n pyta „daj mi token”, worker odpowiada tokenem, a cała kryptografia zostaje po jego stronie. Pełne logowanie trwa około sekundy, a kolejne pobrania tokenu są natychmiastowe, bo worker trzyma sesję w pamięci i sam ją odświeża, zanim wygaśnie.

Problem drugi: jak pokazać fakturę człowiekowi

XML faktury jest świetny dla programów i bezużyteczny dla ludzi. Księgowa, klient czy dział operacji potrzebują widoku, który wygląda jak faktura: tabelka z pozycjami, podsumowanie VAT, dane sprzedawcy i nabywcy. Ministerstwo Finansów nie udostępnia gotowego szablonu takiej wizualizacji dla systemów serwerowych. Opublikowało jedynie bibliotekę napisaną pod przeglądarkę, której nie da się wprost użyć na serwerze.

ksef-pdf-api rozwiązuje to po naszemu: wzięliśmy warstwę renderowania z oficjalnej biblioteki ministerstwa (dzięki temu PDF wygląda dokładnie tak, jak przewiduje wzór), przystosowaliśmy ją do środowiska serwerowego i wystawiliśmy jako proste API. Wysyłasz XML faktury, dostajesz PDF. Serwis sam dorysowuje na fakturze kod QR weryfikacyjny. Każdy może go zeskanować telefonem i sprawdzić na stronie ministerstwa, że faktura naprawdę istnieje w KSeF i nie została podmieniona. Obsługiwane są wszystkie wersje schematu (FA(1), FA(2), FA(3), FA_RR), w tym faktury korygujące.

Jak to działa w całości

Oba serwisy są cegiełkami w przepływach n8n. Typowy scenariusz obsługi faktury wygląda tak:

graph LR A["n8n
(automatyzacja)"] -->|"daj token"| B["ksef-crypto-worker
(logowanie certyfikatem)"] B <-->|"XAdES, tokeny"| K["KSeF
(Ministerstwo Finansów)"] A -->|"token + zapytanie"| K K -->|"XML faktury"| A A -->|"XML"| C["ksef-pdf-api
(wizualizacja)"] C -->|"PDF z kodem QR"| A A --> D["Dysk Google, mail,
księgowość..."]

n8n prosi crypto-workera o token, tokenem pobiera z KSeF fakturę w XML, XML wysyła do pdf-api i dostaje z powrotem PDF, który ląduje tam, gdzie ma wylądować: na dysku, w mailu do kontrahenta, w archiwum. Człowiek w tym łańcuchu nie występuje.

Dlaczego dwa małe serwisy, a nie jeden duży

Oba projekty łączy ta sama filozofia, w README crypto-workera nazwana wprost „świadomie małe”. Każdy serwis robi jedną rzecz, ma jednego klienta (n8n) i działa w zamkniętej sieci Docker, do której nie ma dostępu z zewnątrz. Nie mają baz danych, kolejek, paneli administracyjnych ani rozbudowanej konfiguracji, bo niczego takiego nie potrzebują. Mniej ruchomych części to mniej rzeczy, które mogą się zepsuć o trzeciej w nocy, i mniej powierzchni ataku w usługach, które operują na certyfikatach firmy i danych finansowych.

Z tej samej filozofii wynika podejście do kodu ministerstwa: w obu serwisach oficjalne biblioteki MF są wpięte w pinowanej, konkretnej wersji i traktowane jak zewnętrzna zależność. Podpisów elektronicznych ani wzoru faktury nie pisaliśmy od nowa. Od zgodności z urzędowym standardem jest kod urzędowy, my dokładamy tylko warstwę, która czyni go używalnym w naszej infrastrukturze.

Bezpieczeństwo potraktowaliśmy poważnie w obu miejscach: klucze prywatne nigdy nie dotykają dysku, porównania sekretów są odporne na ataki czasowe, kontenery działają bez uprawnień roota, a pdf-api ma system plików tylko do odczytu. Szczegóły dla zainteresowanych poniżej.

Dla technicznych

Każdy serwis ma osobny artykuł z pełnym opisem architektury, API i decyzji projektowych:

Kod obu serwisów znajdziesz w naszym GitLabie: ksef-crypto-worker i ksef-pdf-api.