How filing works
A Stripe invoice reaches KSeF with no action from you. Here's the whole flow.
Pipeline
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.- 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, and amounts are split into VAT buckets. - 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.
- UPO — on acceptance KSeF returns a KSeF number and a signed UPO (official receipt). That is your legal proof of filing.
- Write-back to Stripe — we write the KSeF number back onto the invoice: into
metadata(machine-readable) and appended tofooter(buyer-visible on the PDF/hosted page).custom_fieldsis immutable after finalization, so it is deliberately never used.
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, or the company profile is incomplete. 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.
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.