KSeF Kit

How filing works

A Stripe invoice reaches KSeF with no action from you. Here's the whole flow.

Who's the buyer? That's the first thing we resolve. If the customer has a tax id (a Polish NIP, an EU VAT number, or a foreign tax id), it's a B2B invoice and goes to KSeF. If there's no tax id, it's a private consumer (B2C) — out of KSeF scope, so we don't file it.

Pipeline

  1. invoice.finalized — Stripe sends a webhook when an invoice is finalized. We resolve the account from the connected Stripe account and store the invoice as an immutable source document. The payload is never mutated afterwards.
  2. FA(3) — we map the Stripe payload to a structured FA(3) invoice (the KSeF schema). Stripe doesn't carry everything: the seller address comes from your company profile, the buyer NIP from customer_tax_ids, the buyer address from the customer's billing address, and amounts are split into VAT buckets. We carry per-line detail, not just rate totals: each Stripe line becomes an FA(3) item with its name (from the line description), quantity, unit net price, net amount and VAT rate. When an invoice has more lines than Stripe fits into one event we ask Stripe for the full list, and a shipping cost gets a position of its own ("Koszt dostawy").
  3. KSeF online session — we open a session, encrypt the invoice (RSA-OAEP + AES-256-CBC, exactly as KSeF requires), send it, and poll for status.
  4. UPO — on acceptance KSeF returns a KSeF number and a signed UPO (official receipt). That is your legal proof of filing.
  5. Write-back to Stripe — we write the KSeF number back onto the invoice's metadata (machine-readable: ksef_number, plus ksef_verification_url for exports). Stripe locks every buyer-visible field (footer, description, custom_fields) once an invoice is finalized — and the KSeF number only exists after filing the finalized invoice — so the number cannot appear on the Stripe PDF or hosted page. The buyer-visible number and the UPO live in the panel; a domestic B2B buyer receives the invoice through KSeF itself.

What you see on screen

Every invoice has one plain status. That's all you need to know what's happening:

Status What it means
Queued Waiting to be sent. We'll file it shortly.
Filing Being sent to KSeF — seconds, not hours.
Filed Accepted. You have a KSeF number and a UPO — that's your proof.
Rejected KSeF didn't accept it. We show the reason and a concrete next step. The invoice waits — you can resubmit.
Held We deliberately didn't send it: the free quota is used up, the company profile is incomplete, or an identified buyer is missing an address. Nothing is lost.

Rejected and Held are two different things. A rejection is KSeF's answer — something in the data or connection needs fixing. A hold is our deliberate decision not to send yet (e.g. over the free quota, or before you've filled in the NIP and address). In both cases the invoice waits safely.

Important — an identified buyer (one with a NIP, EU VAT, or foreign tax id) requires an address. If Stripe has no address for that customer, we hold the invoice rather than file an incomplete one. Add the customer's address in Stripe and refile. To prevent it entirely, require the billing address in Stripe up front — see Connecting KSeF. The fix is detailed in Rejections & troubleshooting.

Attempts are separate records

Every send attempt is its own record (Submission), not an overwrite. A retry creates a new row — so history is auditable and double-submission is impossible. Under the hood the attempt statuses are submitting, accepted, rejected, blocked — these add up to the visible invoice status above.

Filing is blocking

The library's #submit sleeps between status polls — it runs in a background job, never in a web request. If the process crashes after opening a session, #await resumes it from the stored references (idempotently — it never files twice).

Invoice scenarios we cover

KSeF is a B2B/B2G system: the buyer is a business or a public body. Who the buyer is decides the VAT treatment, and the treatment decides which FA(3) fields we emit. Here's what each scenario maps to and where it stands today.

Scenario Buyer VAT treatment FA(3) mapping Status
Domestic PL B2B Polish business (has a NIP) Standard 23% (or 8% / 5% / other PL rates) Buyer NIP; net + VAT in a rated bucket (P_13_1/P_14_1 for 23%) Fully supported
EU B2B Business in another EU state (EU VAT no.) Reverse charge — the buyer accounts for VAT Buyer KodUE + NrVatUE; net-only bucket P_13_9; reverse-charge flag P_18 Supported
Non-EU business Business outside the EU (e.g. a PL sole-proprietor / JDG invoicing a US SaaS company) Export of services — not subject to PL VAT (NP), settled by the buyer Buyer KodKraju + NrID; net-only bucket P_13_8 Supported
B2C consumer A private individual (no business tax ID) Out of KSeF scope Not in KSeF

Every B2B scenario above is built end-to-end — the FA(3) mapping, the markings and the NBP FX. Before we enable the EU and non-EU VAT treatments in production, a Polish tax advisor signs off the exact VAT flags (P_18, the NP buckets) in one review — a correctness gate, not a gradual rollout. You can file all of them in test mode today.

B2C consumers

KSeF is for B2B and B2G. Sales to private consumers are voluntary in KSeF and effectively out of scope for this tool — issue them the way you do now (e.g. a receipt or a regular non-KSeF invoice). If a consumer later asks for an invoice with their NIP, that's a B2B invoice and goes through KSeF normally.

Foreign currency

EU and export invoices are usually in EUR or USD. The złoty amount KSeF needs is computed at the NBP average rate of the last business day before the tax point (for a continuous service — the billing-period end; for an invoice issued in advance — the issue date) — the legally required rate (art. 31a), never Stripe's settlement FX. See Currency & NBP.

This is not tax advice. Confirm the VAT treatment of your specific sales with your accountant.

Notifications

We email the owner about only two things worth interrupting them for: a rejection (you must act) and a filing that just exhausted the free quota (once, at the boundary). See Plans & billing.

When an invoice is rejected: Rejections & troubleshooting.