1. Go to this page and download the library: Download b44x/ksef-php library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
b44x / ksef-php example snippets
use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use B4x\Ksef\Auth\CertificateCredentials;
use B4x\Ksef\Auth\ContextIdentifier;
use B4x\Ksef\Environment;
use B4x\Ksef\Invoice\Address;
use B4x\Ksef\Invoice\Buyer;
use B4x\Ksef\Invoice\BuyerIdentifier;
use B4x\Ksef\Invoice\Invoice;
use B4x\Ksef\Invoice\InvoiceLine;
use B4x\Ksef\Invoice\Seller;
use B4x\Ksef\Invoice\VatRate;
use B4x\Ksef\KsefClient;
use B4x\Ksef\Support\Nip;
$factory = new HttpFactory();
$ksef = KsefClient::builder()
->environment(Environment::Test) // there is no default: choose explicitly
->httpClient(new Client(['timeout' => 30, 'connect_timeout' => 10]), $factory, $factory)
->context(ContextIdentifier::nip('5265877635')) // on whose behalf you work
->credentials(CertificateCredentials::fromPemFiles('/secure/cert.pem', '/secure/key.pem'))
->build();
$invoice = Invoice::builder()
->number('FV/2026/06/001')
->issueDate('2026-06-01')
->seller(new Seller(Nip::of('5265877635'), 'Example Seller sp. z o.o.', Address::poland('ul. Prosta 1', '00-001 Warszawa')))
->buyer(new Buyer(BuyerIdentifier::nip(Nip::of('1234563218')), 'Sample Buyer S.A.'))
->addLine(InvoiceLine::of('Consulting', '10', 'h', '150.00', VatRate::Rate23))
->build(); // throws ValidationException listing every problem
$submission = $ksef->sendInvoice($invoice); // accepted for processing – NOT yet approved
$result = $ksef->waitForInvoice($submission)->assertAccepted();
echo $result->ksefNumber; // e.g. 5265877635-20260601-0100001AF629-15
$upo = $ksef->invoiceUpo($submission); // signed confirmation of receipt (XML)
// 1. Certificate (qualified signature/seal, KSeF certificate, or a self-signed one on TEST).
// The AuthTokenRequest is signed with XAdES-BES (RSA >= 2048 bit or EC P-256+).
CertificateCredentials::fromPemFiles($certPath, $keyPath, $optionalPassphrase);
CertificateCredentials::fromPkcs12(file_get_contents($p12), $password);
// 2. KSeF token (generated in the KSeF application or with $ksef->createToken()).
// Sent as RSA-OAEP(SHA-256) encrypted "token|challengeTimestampMs".
new KsefTokenCredentials(getenv('KSEF_TOKEN'));
$ksef = KsefClient::builder()->...->credentials(new ManualCredentials())->tokenStore($store)->build();
$request = $ksef->createAuthenticationRequest(); // an AuthTokenRequest to sign; valid 10 minutes
// ... the person signs $request->xml (XAdES, enveloped) with their tool and you get the signed document back ...
$ksef->authenticateWithSignedRequest($signedXml); // KSeF verifies it; the client is logged in
KsefClient::builder()
->environment(...) // or ->baseUrl('https://...')
->httpClient($client, $requestFactory, $streamFactory)
->context(...)->credentials(...)
->logger($psr3Logger) // optional; never receives secrets or document contents
->retryPolicy(new RetryPolicy(maxAttempts: 3, baseDelaySeconds: 0.5, maxDelaySeconds: 30.0))
->polling(new PollingPolicy(timeoutSeconds: 120.0)) // default for waitFor*()
->authenticationPolling(...)->allowedIps(...)->publicKeyProvider(...)->clock(...)->sleeper(...)
->build();
$invoice = Invoice::builder()
->number('FV/2026/06/002')->issueDate('2026-06-01')->saleDate('2026-05-31')->issuePlace('Warszawa')
->currency('EUR')->exchangeRate('4.3210') // foreign currency: VAT in PLN is derived
->seller($seller)->buyer($buyer)
->addLine(InvoiceLine::of('Software licence', '1', 'szt.', '1000.00', VatRate::Rate23, 'EUR'))
->addLine(InvoiceLine::of('Training', '2', 'h', '80.00', VatRate::Exempt, 'EUR'))
->annotations(new Annotations(splitPayment: true, exemptionBasis: 'Art. 43 ust. 1 pkt 29 lit. b ustawy o VAT'))
->payment(Payment::dueOn(new DateTimeImmutable('2026-06-15'), PaymentMethod::BankTransfer, ['PL61109010140000071219812874']))
->build();
// Advance invoice (ZAL): the tax is taken out of the gross payment; the lines describe the order.
Invoice::builder()->...->advance(new AdvancePayment(Money::pln('1230.00'), VatRate::Rate23, $paidOn))
->addLine(InvoiceLine::of('Custom software', '1', 'szt.', '5000.00', VatRate::Rate23))->build();
// Final invoice (ROZ): the lines carry the full sale; P_13/P_14/P_15 show what remains after the advances.
Invoice::builder()->...->settlement(new Settlement([AdvanceInvoiceReference::ksef($advanceKsefNumber)], Money::pln('1230.00')))
->addLine(...)->build();
// Correction of an advance invoice (KOR_ZAL): combine correction() and advance(); the advance amount is the
// CHANGE of the payment (negative when it decreases), lines are the order before (asBefore) and after.
Invoice::builder()->...->correction($correction)->advance(new AdvancePayment(Money::pln('-615.00'), VatRate::Rate23, $paidOn))
->addLine($orderLine->asBefore())->addLine($newOrderLine)->build();
// Correction of a final invoice (KOR_ROZ): combine correction() and settlement(); before/after lines.
Invoice::builder()->...->correction($correction)->settlement($settlement)->addLine($line->asBefore())->addLine($newLine)->build();
// Payments received before delivery, shown on the final invoice (ZaliczkaCzesciowa); P_15 stays the total.
Invoice::builder()->...->settlement($settlement)->addPriorPayment(new PriorPayment($receivedOn, Money::pln('500.00')))->addLine(...)->build();
// Collective correction for a period (art. 106j(3)): no lines, differences per rate.
Invoice::builder()->...->correction(new Correction($corrected, period: '2026-04-01 - 2026-06-30', amounts: [CorrectionAmount::of(VatRate::Rate23, '-100.00', '-23.00')]))->build();
// Simplified invoice (UPR): up to PLN 450 / EUR 100, buyer identified by NIP.
Invoice::builder()->...->simplified()->addLine(...)->build();
$submission = $ksef->sendInvoice($invoice); // one invoice, own short-lived session
$session = $ksef->openOnlineSession(); // many invoices, one session (<= 12 h, <= 10,000 invoices)
$a = $session->send($invoiceA);
$b = $session->send($invoiceB);
$session->close(); // starts the aggregate UPO generation
$status = $session->waitUntilFinished(); // SessionStatus incl. UPO page references
$batch = $ksef->sendBatch($invoices); // iterable of Invoice | InvoiceDocument | XML string
$status = $ksef->waitForSession($batch->sessionReference); // SessionStatus (aggregate counts, UPO pages)
$page = $ksef->listSessionInvoices($batch->sessionReference); // per invoice: ksefNumber, status, invoiceHash
// $batch->invoiceHashes lets you map results back to your own documents.
// Entity-level authorisations: self-invoicing, RR, tax representative, Peppol
$ksef->grantAuthorization(EntitySubject::nip(Nip::of('5265877635'), 'Partner sp. z o.o.'), EntityAuthorizationType::SelfInvoicing, 'self-billing');
$ksef->listAuthorizations(AuthorizationDirection::Granted); // ...::Received
$ksef->revokeAuthorization($authorization->id); // not revokePermission(): different endpoint
// Accounting office: a person works in the contexts of your customers
$ksef->grantIndirectPermissions($person, [EntityPermissionType::InvoiceRead], 'staff', IndirectTarget::allPartners());
// Subordinate units (local government, VAT groups) and EU entities
$ksef->grantSubunitAdministrator($person, SubunitContext::internalId('5265877635-12345'), 'branch admin');
$ksef->grantEuEntityAdministrator(EuEntitySubject::person(PersonSubject::byFingerprint(...)), new EuEntity('5265877635-DE123456789', 'Muster GmbH', 'Berlin'), 'admin');
$ksef->grantEuEntityRepresentative(EuEntitySubject::entity($fingerprint, 'Seal GmbH', 'Berlin'), [EuEntityPermissionType::InvoiceWrite], 'rep');
// Reading: listSubunitAdministrators(), listEuEntityPermissions(), listEntityRoles(), listSubordinateEntities(), attachmentStatus()
// Decrypted ZIP of {ksefNumber}.xml + _metadata.json; page through time with continueFrom (PermanentStorage).
$package = $ksef->exportInvoices(InvoiceSubjectType::Buyer, InvoiceDateType::PermanentStorage, $from, null, '/tmp/export.zip');
if ($package->isTruncated) { $from = $package->continueFrom; /* export again */ }
$ksef->contextLimits(); // max invoices / sizes per session type
$ksef->rateLimits(); // ['invoiceSend' => RateLimit(perSecond, perMinute, perHour), ...]
$ksef->listAuthSessions(); // active logins; $ksef->revokeAuthSession($ref) or revokeAuthSession() for the current one
// One payment reference for many invoices of the same seller
$id = $ksef->createCollectiveIdentifier([new CollectiveInvoice($ksefNumber1, Money::pln('123.00'), 'May'), new CollectiveInvoice($ksefNumber2)]);
$page = $ksef->listCollectiveIdentifiers($from, $to); // paged; pass $page->continuationToken for the next page
$ksef->listCollectiveIdentifierInvoices([$id]); // the invoices (payment details only for entitled parties)
$ksef->listCollectiveIdentifiersOf($ksefNumber1); // which identifiers an invoice belongs to
$ksef->listPeppolProviders(); // registered Peppol service providers
$ksef->subjectLimits(); // certificate enrolment / certificate limits of the taxpayer
$cert = $ksef->requestCertificate('billing service', CertificateType::Authentication); // KeyType::EcP256
$credentials = $cert->toCredentials(); // log in with it from now on (no OCSP/CRL delay)
$offline = $ksef->requestCertificate('offline qr', CertificateType::Offline);
$signer = $offline->toOfflineCertificate(); // for VerificationLinks::certificateUrl()
$ksef->certificateLimits(); $ksef->searchCertificates(CertificateType::Offline); $ksef->revokeCertificate($serial);
$links = new VerificationLinks(Environment::Production);
$url = $links->invoiceUrl($sellerNip, $issueDate, $document); // KOD I, every invoice
$label = $links->label($ksefNumber); // KSeF number, or "OFFLINE"
$url2 = $links->certificateUrl($context, $sellerNip, $document->hash(), // KOD II, offline invoices only
new OfflineCertificate($certPem, $keyPem)); // KSeF "Offline" certificate
$renderer = new QrCodeRenderer(300);
$svg = $renderer->svg($url, $links->label($ksefNumber)); // image with "5265877635-20260601-..." or "OFFLINE" below
$svg2 = $renderer->svg($url2, $links->certificateLabel()); // KOD II is captioned "CERTYFIKAT"
$invoice = $ksef->downloadInvoice($ksefNumber)->read(); // or (new InvoiceReader())->read($xmlOrDocument)
$invoice->number; $invoice->issueDate; $invoice->currency; $invoice->type; // InvoiceType
$invoice->seller->name; $invoice->seller->taxId(); $invoice->buyer->address?->line1;
foreach ($invoice->lines as $line) { $line->name; $line->quantity; $line->netAmount; $line->vatRate; }
foreach ($invoice->taxSummary as $row) { $row->group; $row->net; $row->tax; } // P_13_x / P_14_x
$invoice->total; $invoice->payment?->dueDates; $invoice->payment?->bankAccounts;
$invoice->correction?->correctedInvoices; $invoice->advanceInvoices; $invoice->hasAttachment;
$invoice->document->xml; // the original, untouched
$report = $ksef->availability(); // the Ministry's public availability service ("Latarnia"), no authentication
$report->status; // AvailabilityStatus: Available | Maintenance | Failure | TotalFailure
$report->isFailure(); // true when invoices cannot be sent: issue offline invoices
$ksef->availabilityMessages(); // announcements: title, text, start, end (null while unknown)
->tokenStore(new FileTokenStore('/var/lib/myapp/ksef')) // owner-only files in a private directory
->tokenStore(new Psr16TokenStore($privateCache)) // any PSR-16 cache (composer
$upo = $ksef->invoiceUpo($submission);
$upo->verifyHash(); // the hash KSeF announced matches the document
$check = $upo->verifySignature(); // the Ministry's XML signature: valid, signer, certificate dates
$check = $upo->verifySignature($ministryCertificatePem); // ... and `$check->trusted` when the signer is (or was issued by) that certificate
$invoice = $ksef->invoiceStatus($submission); // one request
$invoice = $ksef->waitForInvoice($submission); // polls until terminal; may be a rejection
$invoice->status->isAccepted(); // code 200
$invoice->status->isRejected(); // code >= 400
$invoice->status->isDuplicate(); // 440 -> originalKsefNumber()
$invoice->assertAccepted(); // throws InvoiceRejectedException with KSeF's explanation
$invoice->assertStored(); // like assertAccepted(), but a 440 duplicate counts (already in KSeF)
// An accepted invoice becomes downloadable once `permanentStorageDate` is set:
$ksef->waitForInvoice($submission, null, untilStored: true);
$upo = $ksef->invoiceUpo($submission); // hash announced by KSeF is verified ($upo->verifyHash())
$status = $ksef->sessionStatus($sessionRef); // $status->upoPages -> $ksef->sessionUpo($sessionRef, $page->referenceNumber)
$invoice = $ksef->downloadInvoice($ksefNumber);
// Wait with waitForInvoice(..., untilStored: true) first. As a safety net, a download that still answers
// HTTP 406 (observed for a few seconds after acceptance) raises InvoiceNotAvailableException, or is awaited
// when you pass a PollingPolicy as second argument.
$page = $ksef->searchInvoices(InvoiceSubjectType::Buyer, InvoiceDateType::PermanentStorage, $from, $to);
// Laravel service provider / Symfony service definition: bind KsefClient as a shared service built
// with your framework's PSR-18 client, PSR-3 logger and a secret from your configuration.
$data = new TestData($http, $factory, $factory);
$data->grantPermissions($contextNip, TestIdentifierType::Pesel, $pesel, [Permission::InvoiceRead], 'accountant for tests');
$data->blockContext(ContextIdentifier::nip('5265877635')); // see how your integration reacts to a refused login
$ksef->testData()->setRateLimits(['invoiceSend' => new RateLimit(1, 10, 100)]); // provoke 429 answers
$ksef->testData()->setSessionLimits(new SessionLimits(5, 1, 2), new SessionLimits(7, 1, 2)); // tiny sessions
$ksef->testData()->resetRateLimits(); $ksef->testData()->resetSessionLimits();