Przejdź do treści
KSeF Kit

Integracja Stripe z KSeF: jak faktura ze Stripe staje się FA(3)

Żeby faktury ze Stripe trafiały do KSeF, potrzebujesz trzech rzeczy: czegoś, co nasłuchuje webhooka invoice.finalized, czegoś, co zamienia payload faktury na XML w schemacie FA(3), i czegoś, co uwierzytelnia się w KSeF, otwiera sesję, szyfruje dokument i odbiera numer KSeF wraz z UPO. Możesz to napisać sam, bo protokół jest otwarty i nasza biblioteka też (gem ksef, licencja Apache-2.0), albo podłączyć gotowe: w KSeF Kit łączysz Stripe, podajesz NIP, wklejasz token lub certyfikat i pierwsza faktura idzie do środowiska TEST w kilka minut.

Dalej jest cała mechanika: co dokładnie się dzieje między finalizacją faktury a numerem KSeF, gdzie ta droga najczęściej pęka i ile pracy zostaje po Twojej stronie w każdym z dwóch wariantów. Programista może przejechać po nagłówkach i kodzie, właściciel firmy przeczytać od góry do dołu.

Zanim jednak przejdziemy dalej: obowiązuje schemat FA(3). Jeśli trafiłeś na poradnik, dokumentację albo bibliotekę, która mówi o FA(2), czytasz opis formatu, którego KSeF już nie przyjmuje. To nie jest kosmetyczna zmiana numeru wersji i ma osobną sekcję niżej.

Co musi się wydarzyć, żeby faktura ze Stripe znalazła się w KSeF

Cała droga ma sześć etapów. Żaden z nich nie jest trudny sam w sobie, ale każdy ma miejsce, w którym łatwo zrobić błąd.

  1. Stripe finalizuje fakturę. Do tego momentu faktura jest szkicem i można ją zmieniać. Finalizacja to zdarzenie invoice.finalized, i to ono jest wyzwalaczem. Warto zapisać payload w niezmienionej postaci: to Twój dokument źródłowy i za pół roku będziesz chciał wiedzieć, z czego dokładnie zbudowałeś XML.
  2. Powstaje XML w schemacie FA(3). Z pól faktury Stripe budujesz strukturę Faktura: sprzedawcę (Podmiot1), nabywcę (Podmiot2), kubełki stawek VAT, pozycje, sumy i adnotacje. Kolejność elementów wynika ze schematu XSD i nie jest dowolna.
  3. Uwierzytelniasz się w KSeF. Pobierasz challenge, podpisujesz go tokenem albo podpisem XAdES, odpytujesz o wynik i odbierasz token dostępowy.
  4. Otwierasz sesję i wysyłasz dokument. Sesja niesie klucz AES wygenerowany na tę jedną sesję i zaszyfrowany kluczem publicznym KSeF. Fakturę wysyłasz zaszyfrowaną, razem z jej rozmiarem i skrótem SHA-256.
  5. Zamykasz sesję i odpytujesz o status. Dopiero zamknięcie sesji uruchamia wystawienie UPO. Kiedy dokument zostanie przyjęty, dostajesz numer KSeF i UPO, czyli urzędowe poświadczenie odbioru.
  6. Zapisujesz numer z powrotem w Stripe. Numer KSeF trafia do metadanych faktury (ksef_number), więc widzisz go w panelu Stripe i możesz go odczytać z API. Przy eksporcie dokładamy też ksef_verification_url, czyli adres weryfikacyjny, z którego rysuje się kod QR.

To wszystko musi się zdarzyć dokładnie raz. Faktura wysłana dwa razy to dwa dokumenty w KSeF, a dokumentu z numerem KSeF nie da się anulować. Dlatego rozdziel etap 4 i 5: najpierw zapisz u siebie identyfikator sesji i identyfikator faktury, dopiero potem odpytuj o wynik. Jeśli proces padnie w trakcie odpytywania, masz z czego wznowić i nie kusi Cię, żeby wysłać dokument jeszcze raz. U nas tak właśnie działa Filing#submit: open_and_send zwraca referencje, zapisujemy je w bazie, i dopiero wtedy leci await.

FA(3): co się zmieniło i dlaczego FA(2) już nie przejdzie

FA(3) obowiązuje od 1 lutego 2026 r. FA(2) był ważny od 1 września 2023 r. do 31 stycznia 2026 r. i to jest cała historia: nie ma okresu przejściowego, w którym KSeF przyjmuje oba formaty. Podstawa to ustawa z 5 sierpnia 2025 r. (Dz.U. 2025 poz. 1203). Aktualny schemat to 1-0E, w przestrzeni nazw z datą 2025/06/25.

Kod pisany pod stary schemat najszybciej poznasz po pozycjach faktury. W FA(2) wiersze siedziały w opakowaniu <FaWiersze>. W FA(3) <FaWiersz> jest bezpośrednim, powtarzalnym dzieckiem <Fa> i żadnego opakowania nie ma. Wyemitujesz <FaWiersze>, a KSeF odrzuci fakturę na walidacji schematu, kodem 21401. Tego rodzaju różnic nie widać w opisach ofertowych, a kosztują popołudnie.

Druga rzecz: kolejność elementów w <Fa> jest sekwencją XSD, nie sugestią. Wygląda tak:

KodWaluty · P_1 · P_2 · kubełki P_13_n/P_14_n · P_15 · Adnotacje · RodzajFaktury
  · (blok korekty: PrzyczynaKorekty? · TypKorekty · DaneFaKorygowanej)
  · FaWiersz*

Wyemitujesz to w innej kolejności i sesja kończy się odrzuceniem, mimo że wszystkie dane są na miejscu i poprawne.

Trzecia rzecz, o której często się zapomina: korekty wystawiane dziś idą w FA(3), nawet jeśli oryginał był w FA(2) albo FA(1). Nie ma czegoś takiego jak korekta „w schemacie oryginału”.

I czwarta, ważna dla planowania pracy: kontrakt produkcyjnego API jest zamrożony od 22 grudnia 2025 r. To dobra wiadomość, bo znaczy, że integracja napisana raz nie rozjedzie się co miesiąc. Zła jest taka, że zamrożony kontrakt to również koniec wymówek: to, co dziś nie przechodzi, nie przejdzie samo z siebie za kwartał.

Praktyczny test na jakość źródła: jeśli tekst o integracji ze Stripe mówi „XML zgodny ze schematem FA(2)”, opisuje stan prawny sprzed 1 lutego 2026 r. Cała reszta takiego tekstu, łącznie z wyceną pracy i argumentem „zbuduj albo kup”, opiera się na nieaktualnym założeniu.

Jak wygląda faktura ustrukturyzowana od strony pojęć, opisujemy osobno w jak wystawić fakturę ustrukturyzowaną, a same terminy i progi obowiązku w KSeF obowiązkowy 2026.

Trudne miejsca, uczciwie

Poniżej lista rzeczy, które zajmują najwięcej czasu przy własnej integracji. Nie po to, żeby odstraszyć, tylko żeby nikt nie planował dwóch dni na coś, co ma sześć warstw.

Uwierzytelnienie

Są dwie drogi i obie sprowadzają się do podpisania challenge'a pobranego z API.

Token KSeF (maszyna do maszyny): pobierasz challenge, szyfrujesz łańcuch "{token}|{timestampMs}" algorytmem RSA-OAEP z SHA-256, kluczem publicznym KSeF pobranym z endpointu certyfikatów, wysyłasz do /auth/ksef-token, odpytujesz o wynik i wymieniasz go na token dostępowy. Klasyczna pułapka: token szyfrowany jest dosłownie, więc jedna spacja albo znak nowej linii z kopiowania rozwala uwierzytelnienie i dostajesz kod 450. Dlatego i gem, i sama aplikacja przycinają wklejoną wartość, zanim cokolwiek z nią zrobią.

Podpis XAdES: budujesz AuthTokenRequest w przestrzeni nazw auth/token/2.1, podpisujesz go w wariancie enveloped XAdES-BES (SHA-256 nad kluczem RSA albo EC) i wysyłasz jako application/xml. Dochodzi jeszcze wybór, po czym KSeF ma dopasować certyfikat do podatnika: po polu subject (certyfikat firmowy z NIP-em) albo po odcisku palca (osobisty certyfikat kwalifikowany, który NIP-u nie niesie). Na produkcji łańcuch certyfikatów jest weryfikowany, w TEST i DEMO nie, bo tam używa się certyfikatów samopodpisanych.

Co wybrać, rozkładamy na czynniki w token czy certyfikat KSeF. Skrótowo: opublikowana zasada mówi, że tokeny działają do 31 grudnia 2026 r., a od 1 stycznia 2027 r. zostają same certyfikaty. Ministerstwo Finansów po konsultacjach z 9 czerwca 2026 r. samo zarekomendowało, żeby tokeny jednak zostały, ale to nadal rekomendacja, a nie przepis. Jest przy tym twarde ograniczenie: sesja uwierzytelniona tokenem nie pozwala złożyć wniosku o certyfikat. Z tokena na certyfikat przechodzi więc człowiek, świadomie, a nie skrypt puszczony w nocy.

Szyfrowanie sesji

KSeF nie przyjmuje faktury otwartym tekstem. Na każdą sesję generujesz świeży klucz AES-256 (32 bajty) i wektor inicjujący (16 bajtów), klucz opakowujesz RSA-OAEP z SHA-256 i MGF1-SHA-256, a fakturę szyfrujesz AES-256-CBC z dopełnieniem PKCS#7. Deklarujesz przy tym cztery wartości, nie dwie: rozmiar i skrót SHA-256 jawnego XML-a (invoiceSize, invoiceHash) oraz rozmiar i skrót zaszyfrowanych bajtów (encryptedInvoiceSize, encryptedInvoiceHash), wszystkie w Base64.

Miejsce, w którym prawie każdy się przewraca: w sesji interaktywnej wektor inicjujący idzie osobnym polem initializationVector, a w sesji wsadowej jest doklejony na początek każdej paczki. Pomylisz to i dostajesz 21403 (niezgodny skrót) albo 21402 (niezgodny rozmiar), czyli komunikaty, które nie podpowiadają, gdzie naprawdę leży problem.

Rygor schematu

FA(3) waliduje się twardo i nie wybacza drobiazgów:

Waluty i kursy NBP

Kwota VAT w FA(3) zawsze musi być podana w złotych, w polach P_14_xW. Kurs, którego trzeba użyć, to średni kurs NBP z ostatniego dnia roboczego poprzedzającego moment powstania obowiązku podatkowego (art. 31a). Tabela A NBP publikowana jest tylko w dni robocze, więc odpytujesz krótkie okno wstecz i bierzesz ostatni opublikowany kurs, zamiast zakładać, że wczoraj był dzień roboczy.

Najczęstszy błąd: użycie kursu rozliczeniowego Stripe. To kurs, po którym Stripe przewalutował Twoje pieniądze, i do faktury się nie nadaje. Cała mechanika, razem z przypadkami brzegowymi, jest w kursy walut NBP na fakturze.

Korekty

Nota kredytowa w Stripe (credit_note.created) odpowiada fakturze korygującej. W FA(3) to dokument rodzaju KOR, który w bloku DaneFaKorygowanej wskazuje oryginał: jego numer KSeF, numer własny i datę wystawienia, plus TypKorekty i opcjonalny powód. Korekta jest osobnym dokumentem i przechodzi całą tę samą drogę, z własną sesją i własnym UPO.

Faktury z numerem KSeF nie da się anulować. Jedyną drogą wstecz jest korekta, więc zaprojektuj ścieżkę zwrotów tak, żeby nie zakładała cofnięcia dokumentu. Szerzej w korekta faktury w KSeF (KOR).

Kody odrzuceń

Odrzucenia nie są jednorodne i nie ma sensu obsługiwać ich jednym rescue. Dzielą się na rodziny, a rodzina mówi Ci, po której stronie leży naprawa:

Kod Znaczenie Gdzie naprawiasz
21401 Dokument nie przeszedł walidacji FA(3) dane albo generator XML
21402 / 21403 Niezgodny rozmiar albo skrót transmisja, zwykle wystarczy powtórzyć
21301 / 21304 / 21478 NIP błędny, brakujący albo nieaktywny dane nabywcy
21408 Duplikat, faktura już jest w KSeF nic nie rób, sprawdź swoje ślady
450 / 401 / 403 / 415 / 425 / 460 Uwierzytelnienie albo uprawnienia połączenie z KSeF
21470 Klucz publiczny się zmienił pobierz klucz ponownie i powtórz
429 Za dużo zapytań odczekaj i ponów, honorując Retry-After
440 / 445 / 21440 Sesja anulowana, pusta albo zamknięta otwórz nową sesję
500 / 550 Błąd po stronie KSeF ponów później

Przy pisaniu logiki ponawiania rozstrzyga jedno: 429 i 5xx wolno ponawiać, ale tylko dopóki nie masz jeszcze referencji wysłanego dokumentu. Jeśli wysyłka się udała, a dopiero potem coś padło, faktura jest już w KSeF i ponowna wysyłka zrobi z niej duplikat.

Czego nie ma w danych Stripe

Stripe wygląda na system, który „ma wszystko”. Do zbudowania FA(3) brakuje w nim sześciu rzeczy.

Co dokładnie zbierać w Stripe i jak wymusić to w Checkoucie, rozpisujemy w dane klienta w Stripe do faktury KSeF.

Sprzedaż zagraniczna, czyli najczęściej mylona część

Faktura eksportowa B2B też jest objęta KSeF. Obowiązek wynika z art. 106ga ust. 1 i przywiązany jest do wystawcy, a nie do nabywcy. Polska jednoosobowa działalność, która fakturuje spółkę z Delaware, składa tę fakturę do KSeF tak samo jak fakturę dla firmy z Katowic. Do tego art. 106gb ust. 4 mówi, że dokument przekazywany poza systemem musi nieść numer KSeF i kod QR, bo zagraniczny kontrahent do KSeF się nie zaloguje.

Trzy przypadki, trzy różne dokumenty:

Rozwinięcie z przykładami jest w faktura eksportowa w KSeF, a sam kod QR w kod QR na fakturze KSeF. KSeF tego kodu nie zwraca: liczysz go sam z adresu weryfikacyjnego, NIP-u sprzedawcy, daty wystawienia i skrótu SHA-256 dokładnie tych bajtów XML, które wysłałeś.

Zbudować samemu czy wziąć gotowe

Napiszemy to wprost, choć sprzedajemy narzędzie: samodzielna integracja jest sensowna i wcale nie rzadka. Protokół jest publiczny, Ministerstwo Finansów udostępnia klienty referencyjne, a nasza własna biblioteka protokołu jest otwarta na licencji Apache-2.0, więc nie musisz zaczynać od zera ani prosić nas o zgodę.

Uczciwa ocena pracy, jeśli piszesz w Ruby i korzystasz z gemu: kilka dni na pierwszą fakturę w TEST, a potem od kilku do kilkunastu dni na rzeczy, które decydują o tym, czy to przeżyje kontakt z produkcją. Te rzeczy to:

Jeśli w firmie jest osoba, która to utrzyma, to dobra droga. Jeśli integracja miałaby być jednorazowym projektem bez właściciela, będzie źródłem cichych problemów, bo błędy w KSeF nie manifestują się jako awaria, tylko jako faktura, której nie ma tam, gdzie powinna być. Więcej o samym API w KSeF API dla programistów, a o poligonie doświadczalnym w środowisko testowe KSeF.

Kod: złożenie faktury gemem ksef

Tak wygląda pełna ścieżka, z podziałem na wysyłkę i odpytywanie, czyli tak, jak powinno się to robić w tle:

require "ksef"

environment = Ksef::Environment.new(:test)   # api-test.ksef.mf.gov.pl/api/v2

client = Ksef::Client.new(
  env: environment,
  authenticator: Ksef::Authenticator::Token.new(
    environment: environment,
    ksef_token: ENV.fetch("KSEF_TOKEN"),
    context_nip: "1111111111"
  )
)

invoice = Ksef::Fa3::Invoice.new(
  number: "FV/2026/08/17",
  issue_date: Date.current,
  currency: "PLN",
  seller: Ksef::Fa3::Party.new(name: "Moja Firma sp. z o.o.", nip: "1111111111",
    country: "PL", address_line1: "ul. Przykładowa 1", address_line2: "00-001 Warszawa"),
  buyer: Ksef::Fa3::Party.new(name: "Klient sp. z o.o.", nip: "2222222222",
    country: "PL", address_line1: "ul. Testowa 7", address_line2: "30-001 Kraków"),
  vat_buckets: [
    Ksef::Fa3::VatBucket.new(rate: :standard_23, net: BigDecimal("1000"), vat: BigDecimal("230"))
  ],
  lines: [
    Ksef::Fa3::Line.new(name: "Abonament, sierpień 2026", net: BigDecimal("1000"), rate: :standard_23)
  ],
  total: BigDecimal("1230")
)

# Wysyłka bez odpytywania: zapisz referencje, zanim zaczniesz czekać na wynik.
receipt = client.open_and_send(invoice)
receipt.session_reference   # identyfikator sesji
receipt.invoice_reference   # identyfikator faktury w sesji
receipt.invoice_hash        # Base64URL SHA-256 wysłanych bajtów, czyli wyróżnik do kodu QR
receipt.generated_at        # znacznik DataWytworzeniaFa tych bajtów

# Odpytywanie, bezpieczne do powtarzania: nic nie wysyła ponownie.
result = client.await(
  session_reference: receipt.session_reference,
  invoice_reference: receipt.invoice_reference
)
result.ksef_number          # np. "1111111111-20260817-..."
result.upo.xml              # UPO w oryginale, do przechowania

Kurs NBP i adres weryfikacyjny do kodu QR to osobne, drobne wywołania:

rate = Ksef::NbpRate.lookup(currency: "EUR", date: Date.current)
rate.rate            # kurs średni PLN za 1 EUR
rate.effective_date  # dzień roboczy, z którego pochodzi

Ksef::VerificationCode.new(
  env: :production, nip: "1111111111",
  issue_date: Date.current, invoice_hash: receipt.invoice_hash
).url

Kod biblioteki jest na GitHubie (github.com/startupkit-app/ksef, katalog gems/ksef). Jeśli wolisz uruchomić całą aplikację u siebie, opisujemy to w samodzielnym hostingu.

Co robi KSeF Kit

Po połączeniu Stripe i KSeF aplikacja robi to:

Ile to kosztuje

Plan darmowy: 6 przyjętych faktur miesięcznie, bez karty. Plany płatne to 9 zł, 49 zł i 129 zł miesięcznie i podnoszą wyłącznie limit. Nigdy nie ograniczamy poprawności dokumentu: żaden plan nie odbiera Ci kodu QR, korekt ani eksportu.

Limit liczy tylko faktury przyjęte przez KSeF. Odrzucenie, wstrzymanie ani nieudana próba go nie zjadają. Nie płacisz za to, że coś nie wyszło.

Jak zacząć

Onboarding ma trzy kroki i tyle:

  1. Stripe. Autoryzujesz konto albo instalujesz aplikację z Marketplace'u.
  2. Dane firmy. NIP z weryfikacją w rejestrze MF (biała lista), nazwa i adres sprzedawcy. Kontrola sumy kontrolnej NIP-u dzieje się od razu, więc literówki nie dojdą do KSeF.
  3. KSeF. Wklejasz token albo wgrywasz certyfikat, wybierasz środowisko.

Zaczynaj od TEST. To środowisko z fikcyjnym uwierzytelnianiem, więc możesz tam wysłać cokolwiek i niczego nie zepsujesz. Dopiero kiedy zobaczysz pierwszy numer KSeF i pierwsze UPO, przełączasz się na PROD. Aplikacja podatnika, w której wygenerujesz token albo wniosek o certyfikat, to ap-test.ksef.mf.gov.pl dla testu i ap.ksef.mf.gov.pl dla produkcji.

Jeśli prowadzisz jednoosobową działalność i zastanawiasz się, co dokładnie Cię dotyczy, zacznij od KSeF dla JDG. Jeśli wystawiasz kilka faktur miesięcznie, plan darmowy prawdopodobnie Ci wystarczy: zobacz darmowy KSeF dla Stripe.

Najczęstsze pytania

Jak połączyć Stripe z KSeF?

Trzy kroki: łączysz konto Stripe, uzupełniasz dane firmy razem z NIP-em, podłączasz KSeF tokenem albo certyfikatem. Zaczynasz w środowisku TEST, a po pierwszej udanej fakturze przełączasz się na PROD. Od tego momentu każda sfinalizowana faktura w Stripe idzie do KSeF jako FA(3) bez Twojego udziału.

FA(2) czy FA(3)? Który schemat obowiązuje?

FA(3). Obowiązuje od 1 lutego 2026 r. FA(2) był ważny od 1 września 2023 r. do 31 stycznia 2026 r. i dziś nie przechodzi walidacji. Jeśli poradnik albo biblioteka nadal opisuje FA(2), opisuje format, którego KSeF już nie przyjmuje. Korekty wystawiane teraz też idą w FA(3), nawet gdy oryginał był w FA(2) albo FA(1).

Czy to działa ze Stripe Billing i subskrypcjami?

Tak. Liczy się nie to, skąd faktura pochodzi, tylko to, że została sfinalizowana. Subskrypcja, faktura wystawiona ręcznie w panelu, faktura z Checkoutu czy z API dają ten sam webhook invoice.finalized i tę samą ścieżkę do KSeF. Faktura na kilkanaście pozycji też, bo pełną listę pozycji dociągamy z API Stripe, a nie z samego payloadu.

Co z notami kredytowymi i korektami?

Nota kredytowa w Stripe staje się fakturą korygującą FA(3) typu KOR, która wskazuje numer KSeF oryginału. Faktury, która ma już numer KSeF, nie da się anulować, można ją wyłącznie skorygować. Numer korekty wraca na notę kredytową w Stripe.

Co z klientami zagranicznymi?

Sprzedaż B2B za granicę też jest objęta KSeF, bo obowiązek wynika ze statusu wystawcy, a nie z miejsca nabywcy. Firma z UE to odwrotne obciążenie (samo netto, P_18=1), firma spoza UE to eksport usług poza polskim VAT (samo netto, P_18=2). Sprzedaż do konsumenta pozostaje dobrowolna i domyślnie jej nie składamy.

Potrzebuję tokena czy certyfikatu KSeF?

Na dziś działa jedno i drugie. Token wklejasz raz i integracja rusza od razu. Certyfikat KSeF jest darmowy, ważny dwa lata i to on jest kierunkiem docelowym, bo według opublikowanej zasady tokeny miały działać do 31 grudnia 2026 r. Ministerstwo Finansów zaproponowało zostawienie tokenów na stałe, ale to wciąż rekomendacja, nie przepis.

Ile kosztuje wysyłanie faktur ze Stripe do KSeF?

Plan darmowy obejmuje 6 przyjętych faktur miesięcznie i nie wymaga karty. Plany płatne kosztują 9, 49 i 129 zł miesięcznie i podnoszą wyłącznie limit, nigdy poprawność dokumentu. Odrzucenia i wstrzymania nie zjadają limitu, liczą się tylko faktury przyjęte przez KSeF.

Czy muszę coś robić przy każdej fakturze?

Nie. Finalizacja faktury w Stripe uruchamia całą ścieżkę, a numer KSeF wraca do metadanych dokumentu. Wchodzisz w to ręcznie tylko wtedy, gdy KSeF coś odrzuci albo gdy faktura czeka, bo brakuje w niej danych, których Stripe nie zebrał.