kennzeichenservices / dropshipping-sdk
Production-ready PHP SDK for the Dropshipping API
Installs: 476
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/kennzeichenservices/dropshipping-sdk
Requires
- php: >=8.2
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^2.0
- psr/log: ^3.0
Requires (Dev)
- guzzlehttp/guzzle: ^7.9
- nyholm/psr7: ^1.8
- phpunit/phpunit: ^11.0
README
A PHP SDK for the Kennzeichen Services Dropshipping API. It provides typed request/response objects, webhook processing with middleware pipeline support, and async queue integration.
Features
- Typed endpoints for orders, shipments, products, and webhooks
- Immutable DTOs for all requests and responses
- Webhook processing with configurable middleware pipeline (signature validation, payload validation, deserialization)
- Async webhook processing via queue abstraction
- HMAC-SHA256 webhook signature verification
- Multipart file upload support for emission sticker orders
- PSR-18 HTTP client / PSR-17 HTTP factory compatible (bring your own HTTP client)
- Built-in request/response debug logging via
KS_DROPSHIPPING_DEBUGconstant
Requirements
- PHP 8.2 or higher
- A PSR-18 HTTP client implementation (e.g.
guzzlehttp/guzzle,symfony/http-client) - A PSR-17 HTTP factory implementation (e.g.
guzzlehttp/psr7,nyholm/psr7)
Installation
composer require kennzeichenservices/dropshipping-sdk
Configuration
use Dropshipping\Configuration\DropshippingConfig; $config = new DropshippingConfig( host: 'api.example.com', dropshippingClientId: 123, username: 'your-username', password: 'your-password', webhookSignatureSecret: 'your-webhook-secret', // optional );
Usage
Creating the Client
The client requires a PSR-18 HTTP client and PSR-17 request/stream factories. Example using Guzzle:
use Dropshipping\Client\ApiClient; use GuzzleHttp\Client; use GuzzleHttp\Psr7\HttpFactory; $httpClient = new Client(); $factory = new HttpFactory(); $client = new ApiClient( config: $config, httpClient: $httpClient, psrRequestFactory: $factory, streamFactory: $factory, );
The client exposes four endpoint groups as public readonly properties:
$client->orders-- Order operations$client->shipments-- Shipment operations (license plate reservations)$client->products-- Product operations (availability checks)$client->webhooks-- Webhook operations
Creating an Order
use Dropshipping\DTO\Address; use Dropshipping\DTO\OrderItem; use Dropshipping\DTO\LicensePlateItemCustomization; use Dropshipping\DTO\EuroLicensePlateNumberComponents; use Dropshipping\DTO\Requests\OrderCreationRequest; use Dropshipping\Enums\Gender; $address = new Address( firstName: 'Max', lastName: 'Mustermann', gender: Gender::Male, streetName: 'Musterstraße', houseNumber: '1', zipCode: '12345', cityName: 'Berlin', countryCode: 'DE', ); $item = new OrderItem( productVariantId: 42, name: 'Zulassung', sku: 'ZL-001', quantity: 1, customization: new LicensePlateItemCustomization( licensePlateNumberComponents: new EuroLicensePlateNumberComponents( city: 'B', middle: 'AB', end: '1234', ), ), ); $request = new OrderCreationRequest( externalId: 'order-001', email: 'max@example.com', deliveryAddress: $address, invoiceAddress: $address, items: [$item], ); $response = $client->orders->create($request); echo $response->id; // Order ID
Creating an Emission Sticker Order
use Dropshipping\DTO\Requests\EmissionStickerOrderRequest; $request = new EmissionStickerOrderRequest( externalId: 'sticker-001', email: 'max@example.com', deliveryAddress: $address, invoiceAddress: $address, licensePlateNumberComponents: new EuroLicensePlateNumberComponents( city: 'B', middle: 'AB', end: '1234', ), electric: false, emissionKeyNumber: '0005', filePaths: ['/path/to/fahrzeugschein.pdf'], ); $response = $client->orders->createEmissionStickerOrder($request);
Creating a Reshipped Order
use Dropshipping\DTO\Requests\ReshippedOrderRequest; $request = new ReshippedOrderRequest( externalId: 'reship-001', returnedDeliveryId: 456, deliveryAddress: $address, invoiceAddress: $address, ); $response = $client->orders->createReshippedOrder($request);
Checking License Plate Availability
use Dropshipping\DTO\Requests\AvailabilityCheckRequest; use Dropshipping\Enums\LicensePlateType; use Dropshipping\Enums\VehicleType; $request = new AvailabilityCheckRequest( registrationOfficeServiceId: 1, city: 'B', middle: 'AB', end: '1234', licensePlateType: LicensePlateType::Regular, vehicleType: VehicleType::Car, ); $response = $client->products->checkLicensePlateAvailability($request); foreach ($response->availableLicensePlateNumbers as $plate) { echo "{$plate->city} {$plate->middle} {$plate->end}\n"; }
Reserving a License Plate
use Dropshipping\DTO\Requests\LicensePlateReservationRequest; use Dropshipping\DTO\Requests\LicensePlateReservationCustomization; use Dropshipping\DTO\Requests\LicensePlateReservationVehicleHolder; $request = new LicensePlateReservationRequest( email: 'max@example.com', customization: new LicensePlateReservationCustomization( registrationOfficeServiceId: 1, licensePlateType: LicensePlateType::Regular, vehicleType: VehicleType::Car, licensePlateNumberComponents: new EuroLicensePlateNumberComponents( city: 'B', middle: 'AB', end: '1234', ), ), vehicleHolder: new LicensePlateReservationVehicleHolder( address: $address, ), ); $response = $client->shipments->createLicensePlateReservation($request);
Handling Webhooks
Set up a webhook receiver with the built-in middleware pipeline:
use Dropshipping\Contracts\WebhookHandlerInterface; use Dropshipping\DTO\Webhooks\WebhookEventInterface; use Dropshipping\DTO\Webhooks\DeliveryShipmentEvent; use Dropshipping\Security\WebhookSignatureVerifier; use Dropshipping\Serialization\ArrayMapper; use Dropshipping\Webhook\WebhookDispatcher; use Dropshipping\Webhook\WebhookMessage; use Dropshipping\Webhook\WebhookPipeline; use Dropshipping\Webhook\Middleware\SignatureValidationMiddleware; use Dropshipping\Webhook\Middleware\PayloadValidationMiddleware; use Dropshipping\Webhook\Middleware\DeserializationMiddleware; use Dropshipping\DTO\Webhooks\WebhookEventFactory; use Dropshipping\Enums\WebhookEventType; // Create middleware pipeline $serializer = new ArrayMapper(); $verifier = new WebhookSignatureVerifier($config->getWebhookSignatureSecret()); $pipeline = (new WebhookPipeline()) ->pipe(new SignatureValidationMiddleware($verifier)) ->pipe(new PayloadValidationMiddleware()) ->pipe(new DeserializationMiddleware($serializer, new WebhookEventFactory())); // Implement a handler class ShipmentHandler implements WebhookHandlerInterface { public function supports(WebhookEventInterface $event): bool { return $event->getEventType() === WebhookEventType::DeliveryShipment; } public function handle(WebhookEventInterface $event): void { /** @var DeliveryShipmentEvent $event */ echo "Order {$event->order->id} shipped, tracking: {$event->delivery->trackingCode}\n"; } } // Wire up the dispatcher $dispatcher = new WebhookDispatcher($pipeline, []); $dispatcher->registerHandler(new ShipmentHandler()); // Receive a webhook (e.g. in a controller) $message = new WebhookMessage( payload: $requestBody, signature: $request->getHeaderLine('X-Signature'), webhookId: (int) $request->getHeaderLine('X-Webhook-Id'), webhookVersion: $request->getHeaderLine('X-Webhook-Version'), ); $dispatcher->dispatch($message);
Async Webhook Processing
For high-throughput scenarios, queue webhooks for background processing:
use Dropshipping\Async\QueueWebhookDispatcher; use Dropshipping\Async\WebhookWorker; use Dropshipping\Contracts\WebhookQueueInterface; // Implement WebhookQueueInterface with your queue backend (Redis, RabbitMQ, database, etc.) $queue = new YourQueueImplementation(); // In your HTTP controller: enqueue instead of processing inline $queueDispatcher = new QueueWebhookDispatcher($queue); $queueDispatcher->dispatch($message); // In a background worker process $worker = new WebhookWorker($queue, $dispatcher); $processed = $worker->run(maxMessages: 100);
Architecture Overview
src/
├── Async/ Queue-based webhook processing
├── Client/ API client and HTTP authentication
├── Configuration/ SDK configuration
├── Contracts/ Interfaces for serialization, webhooks, and queues
├── DTO/
│ ├── Requests/ Request objects with toArray() serialization
│ ├── Responses/ Response objects with fromArray() factories
│ └── Webhooks/ Webhook event types and factories
├── Endpoints/ API endpoint classes (Orders, Shipments, Products, Webhooks)
├── Enums/ Backed string enums for type safety
├── Exceptions/ Exception hierarchy
├── Http/ PSR-7 request building and response mapping
├── Security/ HMAC-SHA256 signature verification
├── Serialization/ JSON encode/decode
├── Support/ Input validation utilities
└── Webhook/ Middleware pipeline and dispatcher
The SDK follows these patterns:
- Immutable DTOs -- All request and response objects are
final readonlyclasses. - Static factories -- Response DTOs provide
fromArray()constructors; request DTOs providetoArray()for serialization. - Middleware pipeline -- Webhook processing uses composable middleware (signature validation, payload validation, deserialization).
- PSR compliance -- No HTTP client is bundled. The SDK depends on PSR-18 (HTTP Client), PSR-17 (HTTP Factories), and PSR-7 (HTTP Messages).
Key Components
Endpoints
| Endpoint | Method | Description |
|---|---|---|
$client->orders->create() |
POST /orders | Create a standard order |
$client->orders->createEmissionStickerOrder() |
POST /orders/emissionStickerOrders | Create emission sticker order (multipart) |
$client->orders->createReshippedOrder() |
POST /orders/reshippedOrders | Create reshipped order |
$client->shipments->createLicensePlateReservation() |
POST /licensePlateReservations/reservations | Reserve a license plate |
$client->products->checkLicensePlateAvailability() |
POST /licensePlateReservations/availabilityChecks | Check license plate availability |
Webhook Event Types
| Event | Class | Description |
|---|---|---|
PING |
PingEvent |
Connection test |
DELIVERY_SHIPMENT |
DeliveryShipmentEvent |
Delivery shipped with tracking code |
DELIVERY_RETURN |
DeliveryReturnEvent |
Delivery returned with reason and reshipping offer |
DELIVERY_CANCELLATION |
DeliveryCancellationEvent |
Delivery cancelled |
LICENSE_PLATE_RESERVATION_APPROVAL |
LicensePlateReservationApprovalEvent |
Reservation approved with PIN and price |
LICENSE_PLATE_RESERVATION_REJECTION |
LicensePlateReservationRejectionEvent |
Reservation rejected with alternatives |
LICENSE_PLATE_RESERVATION_TIMEOUT |
LicensePlateReservationTimeoutEvent |
Reservation timed out |
Enums
| Enum | Values |
|---|---|
Gender |
FEMALE, MALE, UNSPECIFIED |
VehicleType |
CAR, MOTORCYCLE |
LicensePlateType |
REGULAR, REGULAR_SEASON, ELECTRIC, ELECTRIC_SEASON, HISTORICAL, HISTORICAL_SEASON |
LicensePlateUsageType |
EURO, PARKING |
ProductType |
LICENSE_PLATE, OTHER |
Extensibility
- Custom HTTP client -- Pass any PSR-18 compliant HTTP client to
ApiClient. - Custom serializer -- Implement
SerializerInterfaceand pass it toApiClientto replace the defaultArrayMapper. - Custom webhook handlers -- Implement
WebhookHandlerInterfaceand register withWebhookDispatcher. - Custom queue backend -- Implement
WebhookQueueInterfacefor async webhook processing with any queue system. - Custom middleware -- Implement
WebhookMiddlewareInterfaceto add processing steps to the webhook pipeline.
Security Considerations
- API authentication uses HTTP Basic Auth. Credentials are added to every request by
ApiKeyAuthenticator. - Webhook payloads are verified using HMAC-SHA256 signatures via the
X-Signatureheader. TheSignatureValidationMiddlewarerejects requests with invalid signatures. - Store API credentials and webhook secrets outside of version control.
Debugging
The SDK supports request/response logging via PHP constants. Define KS_DROPSHIPPING_DEBUG before making API calls to write detailed logs:
define('KS_DROPSHIPPING_DEBUG', true);
By default, logs are written to dropshipping-debug.log in the current working directory. To use a custom log file path:
define('KS_DROPSHIPPING_DEBUG', true); define('KS_DROPSHIPPING_DEBUG_FILE', '/var/log/dropshipping.log');
The debug log includes:
- Timestamp, HTTP method and URL
- Request headers (Authorization is masked)
- Request body
- Response status code and headers
- Response body
- Exception details on transport failures
Example log output:
--------------------------------------------------------------------------------
[2026-02-03 14:30:00] POST https://api.example.com/dropshipping-api/123/2.1.0/orders
>>> REQUEST HEADERS
Authorization: ***
Content-Type: application/json
Accept: application/json
>>> REQUEST BODY
{"externalId":"order-001","email":"max@example.com",...}
<<< RESPONSE 201 Created
Content-Type: application/json
X-Trace-Id: abc-123
<<< RESPONSE BODY
{"id":42,"status":"created"}
--------------------------------------------------------------------------------
Error Handling
All exceptions extend DropshippingException:
| Exception | When |
|---|---|
DropshippingException |
Request DTO field validation failure (e.g. string too long, invalid email, empty required field). Thrown before any HTTP request is made. |
ApiException |
Non-expected HTTP status code from the API. Provides getStatusCode() and getTraceId() for debugging. |
HttpClientException |
PSR-18 client-level transport failure. Wraps the original ClientExceptionInterface. |
WebhookException |
Webhook signature verification or payload validation failure. |
All request DTOs validate their fields against the API spec constraints when constructed. Invalid values throw a DropshippingException with a descriptive message including the field name and the provided value:
// Throws: Field "firstName" must be between 1 and 100 characters, got 110 new Address(firstName: str_repeat('x', 110), ...); // Throws: Field "email" must be a valid email address new OrderCreationRequest(email: 'not-an-email', ...); // Throws: Field "seasonStartMonth" must be between 1 and 12, got 0 new LicensePlateReservationCustomization(seasonStartMonth: 0, ...);
License
Proprietary