KSeF Pro M2DokumentationKSeF Connection & Authentication

KSeF Connection & Authentication

KSeF Pro for Magento 2 uses the same KSeF API authentication model as the PrestaShop and WooCommerce variants. This page covers the authentication flow with Magento-specific details on credential storage via EncryptorInterface, session token caching in Magento's cache backend, and observer integration.

Authentication Flow

KSeF authentication is session-based. Your token initiates sessions — it is not sent with every API call. KSeF Pro executes this flow automatically using Magento's HTTP client (\Magento\Framework\HTTP\Client\Curl):

Step 1: Request challenge
POST /api/online/Session/AuthorisationChallenge
→ Body: { "contextIdentifier": { "type": "onip", "identifier": "YOUR_NIP" } }
← Response: { "challenge": "abc123...", "timestamp": "2025-03-01T10:00:00Z" }

Step 2: Compute HMAC-SHA256 signature
- Decrypt stored token using \Magento\Framework\Encryption\EncryptorInterface
- Compute: HMAC_SHA256(key=decrypted_token, data=challenge+timestamp)

Step 3: Exchange for session token
POST /api/online/Session/AuthorisationToken
→ Body: {
    "challenge": "abc123...",
    "authorisation": "HMAC_SIGNATURE",
    "identifier": { "type": "onip", "identifier": "YOUR_NIP" }
  }
← Response: { "sessionToken": { "token": "SESSION_TOKEN", "context": {...} } }

Step 4: Use SESSION_TOKEN as Bearer for all session API calls
Authorization: Bearer SESSION_TOKEN

KSeF Pro stores the session token in Magento's cache backend (cache tag ksefpl_session_token, TTL 25 minutes) — 5 minutes shorter than the MF-side 30-minute expiry to ensure proactive refresh.

KSeF Pro connection configuration in Magento System Configuration

Credential Storage in Magento

KSeF Pro stores the API token using \Magento\Framework\Encryption\EncryptorInterface. This interface wraps Magento's deployment-specific MAGE_KEY from app/etc/env.php. The encrypted value is saved to core_config_data under the path ksefpl/connection/api_token.

MAGE_KEY Considerations

On-premises: The MAGE_KEY is stable unless you explicitly regenerate it. After any MAGE_KEY rotation, re-enter the KSeF token in Stores → Configuration → PlugKit → KSeF Pro.

Adobe Commerce Cloud: Each cloud environment (Production, Staging, Integration) has a distinct MAGE_KEY. When you clone a database from Production to Staging, the KSeF token in core_config_data is encrypted with the Production key and unreadable in Staging. Re-enter the Staging/test token in the Staging environment.

Best practice for cloud: Store the KSeF token as a cloud environment variable rather than in the database:

magento-cloud variable:set \
  --name=KSEFPL_API_TOKEN \
  --value="your-test-token-here" \
  --sensitive=true \
  --environment=staging

KSeF Pro checks KSEFPL_API_TOKEN in the environment before reading from core_config_data. This approach survives database clones and environment resets.

Diagnosing Decryption Failures

php bin/magento ksefpl:test-connection

If the stored token is unreadable (wrong MAGE_KEY), the output includes: Unable to decrypt stored token — re-enter the API token in configuration. Re-enter the token in the Admin configuration and save.

Test vs. Production Environments

Test Environment (ksef-test.mf.gov.pl)

| Characteristic | Test | Production | |---|---|---| | API URL | https://ksef-test.mf.gov.pl/api | https://ksef.mf.gov.pl/api | | FA(2) schema | Same (2.0 from April 2026) | Authoritative | | Token type | Test tokens only | Production tokens only | | UPOs issued | Yes — functionally complete | Yes — legally binding | | KSeF-IDs | Assigned, no legal standing | Permanent, in VAT ledger | | Legal standing | None | Mandatory |

Validate the following against the test environment before going live:

  1. ksefpl:test-connection returns success
  2. FA(2) XML preview shows correct seller NIP, address, and tax codes
  3. A test Magento invoice completes: PENDINGSUBMITTEDACCEPTED in the audit log
  4. KSeF-ID is written to the Magento invoice record
  5. UPO is downloadable from the audit log

Switching to Production

  1. In Magento Admin: Stores → Configuration → PlugKit → KSeF Pro → KSeF Connection
  2. Change Environment to Production
  3. Replace the test token with your production token
  4. Click "Test KSeF Connection" — green indicator required
  5. Flush config cache: php bin/magento cache:clean config

NIP production check before going live:

curl -s -X POST https://ksef.mf.gov.pl/api/online/Session/AuthorisationChallenge \
  -H "Content-Type: application/json" \
  -d '{"contextIdentifier":{"type":"onip","identifier":"YOUR_10_DIGIT_NIP"}}' \
  | python3 -m json.tool

A challenge field in the response confirms the NIP is registered and active in production KSeF.

NIP Validation

KSeF Pro validates NIP at three checkpoints:

1. On Configuration Save

  • Strips formatting characters
  • Validates 10-digit format
  • Applies weighted checksum algorithm (weights 6,5,7,2,3,4,5,6,7 against digits 1–9; digit 10 = sum mod 11)
  • Red inline error on failure; config cannot be saved with invalid NIP

2. On "Test KSeF Connection" Click

  • Sends a challenge request to the selected environment
  • MF server validates the NIP against the token's registered NIP
  • Error 20100 = NIP mismatch (config NIP differs from token's NIP)
  • Error 10000 = NIP not registered in KSeF for this environment

3. Pre-Queue Entry (Observer)

The InvoiceSaveAfterObserver performs a lightweight pre-flight check before adding a job to ksefpl_queue. If the token is revoked since the last successful connection test, the job is added with status AUTH_ERROR — it will not be submitted until the connection is re-verified in the Admin. This prevents a backlog of FAILED jobs accumulating during a token rotation.

Observer Integration

KSeF Pro hooks into Magento's invoice lifecycle via the observer pattern, not event plugins:

<!-- PlugKit/KsefPl/etc/events.xml -->
<event name="sales_order_invoice_save_after">
    <observer name="ksefpl_invoice_queue"
              instance="PlugKit\KsefPl\Observer\InvoiceSaveAfterObserver"
              sortOrder="200" />
</event>
<event name="sales_order_creditmemo_save_after">
    <observer name="ksefpl_creditmemo_queue"
              instance="PlugKit\KsefPl\Observer\CreditMemoSaveAfterObserver"
              sortOrder="200" />
</event>

sortOrder="200" ensures KSeF Pro fires after other invoice observers (e.g., Fakturownia Pro, ERP connectors). Change the sort order in your custom module's di.xml if KSeF Pro needs to fire in a different position.

Both observers only queue the submission — they complete in milliseconds and never make HTTP calls to the KSeF API. Actual API calls happen asynchronously via the cron jobs.

Session Token Lifecycle

| State | Cache Tag | DB Status | Description | |---|---|---|---| | No session | No cache entry | — | Module idle | | Authorising | ksefpl_auth_pending | — | Challenge/response in progress | | Active | ksefpl_session_token | ksefpl_sessions.status=active | Session open; invoices can be submitted | | Sending | Session token + lock | status=sending | Batch in progress | | Closing | Session token | status=closing | Waiting for MF UPO | | Closed | Cache cleared | status=closed | UPO received; KSeF-IDs assigned | | Expired | Cache TTL elapsed | status=expired | Must open new session | | Terminated | Cache cleared | status=terminated | Invoices NOT accepted |

The terminated state is the critical one. Any invoices in a terminated session were not accepted by KSeF and have no KSeF-IDs. They must be resubmitted. KSeF Pro marks them as FAILED in the audit log with reason SESSION_TERMINATED.

Firewall and Network Requirements

KSeF Pro uses \Magento\Framework\HTTP\Client\Curl for all outbound API calls. Required outbound HTTPS connections:

| Host | Port | Required for | |---|---|---| | ksef-test.mf.gov.pl | 443 | Test environment API | | ksef.mf.gov.pl | 443 | Production environment API | | api.nbp.pl | 443 | NBP exchange rates (non-PLN invoices) |

Adobe Commerce Cloud

Outbound HTTPS to arbitrary hosts is allowed by default on all Adobe Commerce Cloud environments. No allowlist changes are needed.

On-Premises Firewall

If your Magento server is behind an outbound firewall, add all three hostnames to the allowlist. Use hostname-based rules — MF and NBP IP addresses change periodically.

Test connectivity from the Magento server:

# Test KSeF API
php bin/magento ksefpl:test-connection
 
# Test manually
curl -sI https://ksef-test.mf.gov.pl \
  | head -1
# Expected: HTTP/2 200 or HTTP/2 405

Multi-Store Scope Architecture

Magento's scope hierarchy (Default → Website → Store View) applies to KSeF Pro configuration. Each website scope can have its own KSeF token and NIP for stores operating under separate legal entities.

| Scope | When to use | |---|---| | Default | Single legal entity, one KSeF account for all stores | | Website | Multiple legal entities — different NIPs, different tokens per website | | Store View | Not recommended for KSeF settings — NIP is a legal entity property, not locale-specific |

To configure at website scope:

  1. In Stores → Configuration, use the Store View dropdown (top-left) to select the target website
  2. Uncheck "Use Default" next to the API token and NIP fields
  3. Enter website-specific values
  4. Save — these settings override Default scope for all orders from this website

Check effective NIP at a given scope:

php bin/magento config:show ksefpl/seller/nip --scope=websites --scope-code=<website_code>

Token Rotation

When rotating your KSeF token (recommended annually):

  1. Generate the new token on the KSeF portal — old and new tokens are valid simultaneously
  2. In Magento Admin, enter the new token and click "Test KSeF Connection"
  3. Save configuration — the new token replaces the old one in core_config_data
  4. Allow any in-flight sessions using the old token to complete (check ksefpl_sessions for active status)
  5. Delete the old token on the KSeF portal

On Adobe Commerce Cloud, update the environment variable if used:

magento-cloud variable:set --name=KSEFPL_API_TOKEN --value="new-token" --sensitive=true

Next Steps

Edit this page on GitHub
Was this page helpful?

Verwandte Dokumentation

Dieses Thema ist auch für andere Plattformen verfügbar: