naprawksef / sdk
Official PHP SDK for the NaprawKSeF API (KSeF XML validation, FA(3) correction builder, webhooks). Zero non-stdlib runtime dependencies; works with Laravel, Symfony and plain PHP.
Requires
- php: >=8.1
- ext-curl: *
- ext-json: *
Requires (Dev)
- phpunit/phpunit: ^10.0
README
Official PHP SDK for the NaprawKSeF API.
- Full coverage of
/api/v1: validate, correction analyze, scenarios, webhooks, me, health - Built-in retries (429, 5xx, network), respects
Retry-After - Automatic
Idempotency-Keygeneration for POST requests - Webhook signature verification helper (constant-time HMAC-SHA256)
- Zero runtime dependencies — uses native
ext-curl
Requirements
- PHP 8.1+
- ext-curl
- ext-json
Installation
composer require naprawksef/sdk
Quick start
<?php use NaprawKsef\Sdk\NaprawKsef; use NaprawKsef\Sdk\Exceptions\RateLimitException; use NaprawKsef\Sdk\Exceptions\ValidationException; require __DIR__ . '/vendor/autoload.php'; $client = new NaprawKsef([ 'api_key' => $_ENV['NAPRAW_KSEF_API_KEY'], // nk_live_... or nk_test_... 'app_name' => 'trawers-erp/7.4.2', // optional, attached to User-Agent ]); // 1. Who am I? $me = $client->me->retrieve(); echo $me['organization']['name'], PHP_EOL; // 2. Validate a KSeF XML $xml = file_get_contents('invoice.xml'); try { $result = $client->validate->run($xml, filename: 'invoice.xml'); if (!$result['valid']) { foreach ($result['issues'] as $issue) { printf("[%s] %s → %s\n", $issue['severity'], $issue['code'], $issue['message']); } } } catch (RateLimitException $e) { printf("Throttled. Wait %d seconds.\n", $e->retryAfterSeconds() ?? 60); } catch (ValidationException $e) { printf("Bad input: %s (%s)\n", $e->getMessage(), $e->code); } // 3. Analyse for FA(3) correction scenarios $analysis = $client->correction->analyze($xml); print_r($analysis['suggestions']); // top 5 scenarios with confidence
API keys
API keys come in two environments:
| Prefix | Environment | Quota | History |
|---|---|---|---|
nk_live_* |
production | counts against your plan | persisted in dashboard |
nk_test_* |
sandbox | bypassed | not persisted |
The SDK rejects malformed keys at construction time (44 chars, nk_<env>_<4 hex prefix><32 hex secret>).
Idempotency
POSTs to mutating endpoints accept an idempotency key (8–200 chars). The SDK auto-generates a UUID v4 when you don't supply one — pass your own for end-to-end replay safety:
$client->validate->run( xml: $xml, idempotencyKey: "invoice-{$invoice->id}-v3", );
Replays return the original response for 24 hours. Conflicting bodies under
the same key yield an IdempotencyConflictException.
Errors
All API errors are subclasses of NaprawKsefException:
use NaprawKsef\Sdk\Exceptions\{ NaprawKsefException, AuthException, ForbiddenException, NotFoundException, ValidationException, RateLimitException, IdempotencyConflictException, ServerException, NetworkException, };
Each exception exposes $status, $code (stable string, e.g.
QUOTA_EXCEEDED), $requestId, $headers and $details.
RateLimitException and IdempotencyConflictException expose
retryAfterSeconds() to make retry loops easy.
Webhooks
Verify incoming events with the standalone helper (no SDK client needed):
<?php use NaprawKsef\Sdk\Webhooks\SignatureVerifier; require __DIR__ . '/vendor/autoload.php'; $rawBody = file_get_contents('php://input'); $header = $_SERVER['HTTP_X_NK_SIGNATURE'] ?? null; $result = SignatureVerifier::verify( secret: $_ENV['NAPRAW_KSEF_WEBHOOK_SECRET'], rawBody: $rawBody, header: $header, ); if (!$result['ok']) { http_response_code(401); exit('Invalid signature: ' . $result['reason']); } $event = json_decode($rawBody, true); // handle $event['type'] (e.g. "validation.completed") http_response_code(200); echo 'ok';
Laravel controller:
public function __invoke(Request $request) { $result = SignatureVerifier::verify( secret: config('services.naprawksef.webhook_secret'), rawBody: $request->getContent(), header: $request->header('X-NK-Signature'), ); abort_if(!$result['ok'], 401, 'Invalid signature: ' . $result['reason']); $event = $request->json()->all(); // … return response('ok'); }
Retries
408, 425, 429, 500, 502, 503, 504 and network errors are
retried up to max_retries (default 3) with exponential backoff +
±20 % jitter. Retry-After is honoured on 429/503.
$client = new NaprawKsef([ 'api_key' => $apiKey, 'max_retries' => 0, // disable retries 'timeout_ms' => 60_000, // longer per-request timeout ]);
Testing
composer install
composer test
The included PHPUnit suite covers signature verification end-to-end.
License
MIT. See LICENSE.