KSeF Pro WCDocumentationSending Invoices to KSeF

Sending Invoices to KSeF

KSeF Pro for WooCommerce generates FA(2) XML documents from WooCommerce order data, signs them with XAdES-BES digital signatures, and manages the full KSeF session lifecycle using WordPress cron for asynchronous processing.

Automatic Submission Flow

When a WooCommerce order reaches the configured trigger status, the full pipeline runs asynchronously:

1. woocommerce_order_status_changed action fires
2. KSeF Pro rule engine evaluates the order
3. FA(2) XML generated from order data
4. XML signed with XAdES-BES (PHP OpenSSL)
5. Job inserted into {prefix}_ksefpl_queue table
6. Order status change completes instantly (no API wait)

--- Background (WordPress cron) ---
7. ksefpl_process_queue cron: opens KSeF session, submits batch
8. ksefpl_poll_status cron: polls MF server for session result
9. ksefpl_download_upo cron: downloads UPO when ready
10. KSeF-ID written to order meta (_ksefpl_ksef_id)

The customer never waits for KSeF API calls. Steps 1–5 complete in milliseconds during the order status change. Steps 7–10 happen silently in the background within 1–5 minutes depending on cron frequency and MF processing time.

FA(2) XML Generation

KSeF Pro maps WooCommerce order fields to FA(2) logical structure elements:

| WooCommerce Source | FA(2) Element | Notes | |---|---|---| | Order date | <DataWystawienia> | Europe/Warsaw timezone enforced | | KSeF Pro seller config | <Podmiot1> (seller block) | NIP, name, address | | Order billing name + NIP meta | <Podmiot2> (buyer block) | B2B: NIP present; B2C: BRAK | | Line items (name, qty, price) | <FaWiersz> rows | One row per product SKU | | WooCommerce tax classes | <StawkaPodatku> | Mapped to 23, 08, 05, 0, ZW, NP | | Order subtotal (excl. tax) | <P_13_1> / <P_13_2> etc. | Per tax rate net amounts | | Order tax amounts | <P_14_1> / <P_14_2> etc. | Per tax rate VAT amounts | | Order total (incl. tax) | <P_15> | Total gross in PLN | | Shipping method name | <P_7> in shipping row | Unit: usł. | | Order currency + rate | <KursWaluty> | Non-PLN orders only |

Product Name Sanitisation

WooCommerce allows any characters in product names. FA(2) XML requires valid XML character encoding. KSeF Pro sanitises product names automatically:

  • Strips control characters (ASCII 0–31 except tab, newline, carriage return)
  • Encodes XML special characters (<, >, &, ", ')
  • Truncates names longer than 256 characters with a warning in the debug log

If a product name causes XML validation errors after sanitisation (e.g., emoji or non-Latin scripts), rename the product to standard alphanumeric characters.

Zero-Price Line Items

Free items (price = 0.00) included in WooCommerce orders can produce invalid FA(2) elements. Enable "Skip zero-price items" in Advanced settings to exclude them from the FA(2) document. They are still on the WooCommerce order — only omitted from the KSeF submission.

WooCommerce Coupons and Discounts

WooCommerce coupon discounts are not separate FA(2) line items. They are distributed proportionally across the affected line items, reducing the unit price of each. KSeF Pro applies coupon allocation using WooCommerce's get_line_subtotal() method, which accounts for the discount. The final FA(2) line item prices are post-discount.

XML Preview

Before enabling production submission, review the XML for a real order:

  1. Go to WooCommerce → Orders → open any order
  2. In the KSeF Pro order meta box, click "Preview FA(2) XML"
  3. The XML opens in a modal — verify seller NIP, buyer NIP (or BRAK), line items, VAT rates, and totals

XML signing and preview screen for WooCommerce

Pay particular attention to:

  • <P_15> (gross total) matching the WooCommerce order total in PLN
  • Each <FaWiersz> row has the correct <StawkaPodatku> VAT code
  • <Podmiot1> NIP matches your seller configuration
  • <Podmiot2> NIP is populated for B2B orders

XAdES-BES Signing

Before submission, every FA(2) XML is signed with an XAdES-BES enveloped signature. This is required by KSeF — unsigned documents are rejected.

KSeF Pro signs using PHP's OpenSSL extension:

  1. Canonicalize the FA(2) XML (C14N normalization)
  2. Compute SHA-256 digest of the canonical XML
  3. Sign the digest with HMAC-SHA256 using the KSeF token as key
  4. Embed the <ds:Signature> block in the FA(2) XML
  5. Base64-encode the signed document for API transport

No qualified certificate is needed. The KSeF portal token is sufficient for XAdES-BES. Qualified certificates are only required for token issuance at the portal.

WordPress Cron Session Lifecycle

KSeF Pro registers four WP-Cron events that manage the session lifecycle:

| Cron Hook | Default Interval | What It Does | |---|---|---| | ksefpl_process_queue | 60 seconds | Opens a KSeF session; submits all PENDING jobs in batches of up to 50 | | ksefpl_poll_status | 60 seconds | Calls GET /common/Status/{referenceNumber} for open sessions | | ksefpl_download_upo | 120 seconds | Downloads UPO XML for sessions where MF processingCode=200 | | ksefpl_retry_failed | 300 seconds | Re-queues FAILED jobs within retry limit |

Cron Reliability Warning

WordPress HTTP-triggered cron (wp-cron.php) fires only when a page loads. On low-traffic sites this means the cron may not run for minutes or hours. System cron is mandatory for production reliability.

# Disable HTTP trigger in wp-config.php
define('DISABLE_WP_CRON', true);
 
# System cron entry (every 5 minutes)
*/5 * * * * cd /var/www/yoursite && wp cron event run --due-now --quiet

Verify cron is running after setup:

wp cron event list --fields=hook,next_run_relative | grep ksefpl

All four ksefpl_* hooks should show a next run time in the near future (within the configured interval).

Batch Session Processing

Multiple invoices are batched into a single KSeF session to minimise API round trips:

ksefpl_process_queue fires
  → Fetch up to 50 PENDING jobs from queue
  → Open KSeF session (POST /online/Session/InitSigned)
  → Submit invoice 1 (order 1001) → elementRef A
  → Submit invoice 2 (order 1002) → elementRef B
  → Submit invoice 3 (order 1003) → elementRef C
  → Close session (POST /online/Session/CloseSigned)
  → Store session referenceNumber in {prefix}_ksefpl_sessions
  → Mark all 3 jobs as SUBMITTED

ksefpl_poll_status fires (60s later)
  → GET /common/Status/{referenceNumber}
  → processingCode: 200 → ready

ksefpl_download_upo fires
  → Download UPO XML
  → Extract KSeF-IDs: 1001→KSeF-ID-A, 1002→KSeF-ID-B, 1003→KSeF-ID-C
  → Write KSeF-IDs to order meta
  → Mark jobs as ACCEPTED
  → Store UPO at wp-content/uploads/ksefpl-upo/2025/03/{order-id}.xml

Batch session management and status panel

Session Timeout Risk

KSeF sessions expire after 30 minutes of inactivity. For stores where cron runs infrequently (e.g., every 5 minutes but processing takes longer due to hosting), a session may time out between submit and close.

Mitigation: enable "Close after each invoice" in Advanced settings. This opens a dedicated session per invoice (slower but zero timeout risk). Recommended for stores submitting fewer than 20 invoices per hour.

Manual Submission

For individual orders, submit without waiting for the cron queue:

  1. Go to WooCommerce → Orders �� [Order detail]
  2. In the KSeF Pro meta box, click "Submit to KSeF now"
  3. KSeF Pro opens a single-invoice session synchronously — the page waits for completion (typically 5–15 seconds)
  4. The result (KSeF-ID or error) appears in the meta box immediately

Manual submission bypasses the cron queue and is synchronous. Use it for:

  • Retrying a specific failed order immediately
  • Orders that need immediate KSeF registration (e.g., a large B2B order with an impatient buyer)
  • Testing the full pipeline on a single order before enabling auto-submission for all

WP-CLI Commands

KSeF Pro registers WP-CLI subcommands for queue management and debugging:

# Submit a specific order immediately (equivalent to clicking "Submit now")
wp ksefpl submit 1001
 
# Check KSeF submission status for an order
wp ksefpl status 1001
 
# Retry all FAILED submissions within retry limit
wp ksefpl retry-failed
 
# Show queue statistics
wp ksefpl queue --format=table
 
# Run the process queue cron manually
wp cron event run ksefpl_process_queue
 
# Run full cycle: process + poll + download UPO
wp cron event run ksefpl_process_queue && \
  sleep 5 && \
  wp cron event run ksefpl_poll_status && \
  wp cron event run ksefpl_download_upo

UPO Content and Storage

The UPO (Urzędowe Poświadczenie Odbioru) is the official government proof of invoice receipt. KSeF Pro stores it at:

wp-content/uploads/ksefpl-upo/{year}/{month}/{order-id}.xml

Each UPO XML contains:

  • Permanent KSeF-ID
  • Submission and acceptance timestamps
  • Ministry of Finance cryptographic seal
  • SHA-256 hash of the original FA(2) XML

UPOs must be retained for 5 years under Polish tax law. KSeF Pro does not auto-purge them. At minimum, include these files in your regular backup procedure.

Download a UPO from the audit log: WooCommerce → KSeF Pro → Audit Log → find the ACCEPTED record → click the UPO icon.

Currency Conversion Details

For non-PLN WooCommerce stores:

| Scenario | How KSeF Pro Handles It | |---|---| | PLN order | Amounts used directly; no <KursWaluty> element | | EUR order, WPML stores exchange rate | Stored rate used; <KursWaluty> populated | | EUR order, no stored rate | NBP API fetched at submission time; rate cached in WP transients for 24h | | Exotic currency, NBP not available | Manual fallback rate from Advanced settings; warning in audit log |

The NBP API endpoint: https://api.nbp.pl/api/exchangerates/rates/A/{currency}?format=json. Ensure this host is reachable from your server.

Next Steps

Edit this page on GitHub
Was this page helpful?

Documentation associée

Ce sujet est également disponible pour d'autres plateformes :