Home
DHL Shipping Middleware for Picqer
This Laravel application serves as a middleware between Picqer and DHL Parcel DE Shipping API. It receives webhook calls from Picqer with shipment data, processes that data, creates shipments via the DHL API, and returns shipping labels and tracking information back to Picqer.
Features
- Webhook integration with Picqer
- DHL Parcel DE Shipping API integration
- OAuth2 Password Grant (ROPC) authentication with DHL API
- Automatic shipping label retrieval and storage (base64-decoded PDF)
- Webhook signature verification
- Sandbox and Production mode support
- Automatic country code conversion (ISO alpha-2 to alpha-3)
- Shipment tracking and status management
- Database storage of all shipments and labels
- Dark mode web interface with shipments overview and documentation
Web Interface
The middleware includes a professional dark mode web interface with three main sections:
- Home (
/) - Dashboard with system status and quick links - Shipments (
/shipments) - View all shipments, download labels, track packages - Documentation (
/docs) - Complete setup guides and API references
Features
- Real-time shipment tracking
- One-click label downloads
- Status filtering and pagination
- System health monitoring
- Complete API documentation
- Mobile responsive design
Quick Start
-
Start the development server:
php artisan serveThen visit
http://localhost:8000 -
Or continue reading for setup instructions...
Requirements
- PHP 8.2 or higher
- Composer
- SQLite (default) or MySQL/PostgreSQL
- DHL Parcel DE API credentials
- Picqer account with custom shipping method
Installation
-
Install dependencies:
composer install -
Configure environment: The
.envfile has already been created. Generate an application key if needed:php artisan key:generate -
Run migrations: Migrations have already been run. If you need to run them again:
php artisan migrate -
Configure environment variables: Edit
.envand add your credentials:# DHL Configuration DHL_MODE=sandbox # or 'production' # Sandbox credentials (you need all 4!) # From developer.dhl.com: DHL_SANDBOX_CLIENT_ID=your_sandbox_client_id DHL_SANDBOX_CLIENT_SECRET=your_sandbox_client_secret # From DHL Business Portal (GKP): DHL_SANDBOX_USERNAME=your_gkp_username DHL_SANDBOX_PASSWORD=your_gkp_password # Production credentials (when ready - all 4 again) DHL_PRODUCTION_CLIENT_ID=your_production_client_id DHL_PRODUCTION_CLIENT_SECRET=your_production_client_secret DHL_PRODUCTION_USERNAME=your_production_gkp_username DHL_PRODUCTION_PASSWORD=your_production_gkp_password # DHL Shipper details # Sandbox: use 33333333330101 # Production: use your actual billing number DHL_BILLING_NUMBER=33333333330101 DHL_SHIPPER_NAME="Your Company Name" DHL_SHIPPER_STREET="Your Street" DHL_SHIPPER_HOUSE_NUMBER="123" DHL_SHIPPER_POSTAL_CODE="12345" DHL_SHIPPER_CITY="Your City" DHL_SHIPPER_COUNTRY=DE DHL_SHIPPER_EMAIL="[email protected]" DHL_SHIPPER_PHONE="+49301234567" # Label format is configured in config/dhl.php (default: A6 format) # Available formats: 910-300-410 (A6), 910-300-700 (A4), 910-300-710 (A4 4-up), etc. # Picqer Configuration PICQER_WEBHOOK_SECRET=your_webhook_secret PICQER_VERIFY_SIGNATURE=trueImportant: DHL authentication requires 4 credentials:
- 2 from developer.dhl.com (API Key/Client ID + Client Secret)
- 2 from your DHL Business Customer Portal/GKP (Username + Password)
-
Start the development server:
php artisan serve
API Endpoints
Picqer Webhook Endpoints
Get Configuration Options
- URL:
GET /api/webhook/picqer/config-options - Description: Returns available shipping configuration options for Picqer
- Authentication: HTTP Basic Auth (optional)
- Response:
{ "version": "1.0", "data": { "items": [ { "key": "shipping_method", "label": "Shipping Method", "description": "Select the DHL shipping product", "type": "list", "possible_values": [ {"value": "V01PAK", "label": "DHL Paket (Standard)"}, {"value": "V01PRIO", "label": "DHL Paket (Priority)"}, {"value": "V06PAK", "label": "DHL Paket International"}, {"value": "V53WPAK", "label": "DHL Europaket"} ], "default_value": "V01PAK", "required": true } ] } }
Create Shipment
- URL:
POST /api/webhook/picqer/shipment - Description: Receives webhook from Picqer to create a shipment
- Authentication: HTTP Basic Auth (optional) + Signature Verification
- Headers:
X-Picqer-Signature: HMAC signature (if verification enabled)
- Request Body: Picqer webhook payload (see Picqer Integration section)
- Success Response (201):
{ "version": "1.0", "data": { "carrier_key": "dhl", "carrier_name": "DHL", "tracking_code": "00340434161094015902", "tracking_url": "https://www.dhl.de/de/privatkunden/pakete-empfangen/verfolgen.html?piececode=00340434161094015902", "label": { "format": "A6", "documents": { "pdf": "BASE64_ENCODED_PDF_DATA", "zpl": "BASE64_ENCODED_ZPL_DATA" } } } } - Error Response (4xx):
{ "version": "1.0", "error_messages": [ "Recipient country not allowed for this product", "Invalid postal code format" ] }
Shipment Management Endpoints
List All Shipments
- URL:
GET /api/shipments - Description: Retrieve paginated list of all shipments
- Query Parameters:
per_page(optional): Items per page (default: 20)status(optional): Filter by status (pending, processing, created, failed, cancelled)
- Response:
{ "success": true, "data": [...], "pagination": { "total": 50, "per_page": 20, "current_page": 1, "last_page": 3 } }
Get Shipment Information
- URL:
GET /api/shipments/{shipmentId} - Description: Retrieve shipment details
- Response:
{ "success": true, "data": { "id": 1, "status": "created", "tracking_number": "00340434161094015902", "dhl_shipment_number": "00340434161094015902", "tracking_url": "https://www.dhl.de/...", "label_url": "http://localhost:8000/api/shipments/1/label", "created_at": "2025-11-07T13:45:20.000000Z", "shipped_at": "2025-11-07T13:45:25.000000Z" } }
Get Shipping Label
- URL:
GET /api/shipments/{shipmentId}/label - Description: Download the shipping label (PDF or ZPL)
- Response: Binary PDF or ZPL file
Health Check
- URL:
GET /api/health - Description: Check API status and configuration
- Response:
{ "status": "ok", "timestamp": "2025-11-07T13:45:20+00:00", "mode": "sandbox" }
Picqer Integration
Setting Up External Shipping Provider
This middleware is fully compliant with Picqer's External Shipping Provider Integration specification.
In Picqer:
- Go to Settings → Shipping Methods
- Create a new External Shipping Provider (or Custom Shipping Method)
- Configure the endpoints:
- Configuration URL:
https://your-domain.com/api/webhook/picqer/config-options(optional) - Shipment Creation URL:
https://your-domain.com/api/webhook/picqer/shipment
- Configuration URL:
- Choose Authentication Method:
- HTTP Basic Auth (optional - username/password)
- Or rely on webhook signature verification
- Add the webhook secret in your
.env:PICQER_WEBHOOK_SECRET=your_secret_here
Picqer Webhook Payload Format
Picqer sends webhooks in the following structure (compliant with their API specification):
{
"version": "1.0",
"data": {
"profile": [
{
"key": "shipping_method",
"value": "V01PAK"
},
{
"key": "notification_email",
"value": 1
}
],
"shipment": {
"weight": 1500,
"total_paid": 99.99,
"value_of_goods": 79.99,
"recipient": {
"name": "John Doe",
"contactname": "Jane Doe",
"address": "Hauptstraße",
"address2": "123",
"zipcode": "10115",
"city": "Berlin",
"region": "Berlin",
"country": "DE",
"emailaddress": "[email protected]",
"telephone": "+49123456789"
},
"references": {
"reference": "Order #12345",
"idorder": 12345,
"idreturn": null,
"idpicklist": 67890,
"orderid": "O2025-12345",
"returnid": null,
"picklistid": "P2025-67890"
},
"preferred_delivery_date": "2025-11-15",
"pickup_point_data": [],
"language": "en",
"packaging": {
"idpackaging": 123,
"length": 30,
"width": 20,
"height": 15
}
}
}
}
Customs Data Support
For shipments outside the EU, Picqer includes customs_data in the webhook:
{
"version": "1.0",
"data": {
"shipment": {
"customs_data": {
"products": [
{
"description": "Winter Scarf",
"amount": 2,
"weight": 350,
"value": 29.99,
"hs_code": "288392",
"country_of_origin": "CN"
}
]
}
}
}
}
The middleware automatically:
- Detects customs data presence
- Transforms it to DHL's customs format
- Returns A4 format labels (including CN23/commercial invoice)
- For EU shipments: returns A6 format labels
Configuration Options
The middleware provides these configurable options in Picqer (via /config-options endpoint):
| Option | Type | Description | Default |
|---|---|---|---|
shipping_method |
List | DHL product (V01PAK, V01PRIO, V06PAK, V53WPAK) | V01PAK |
notification_email |
List | Send tracking email to recipient | Yes |
reference_note |
Text | Optional note on label | null |
Response Format
The middleware returns Picqer-compliant responses:
Success Response:
{
"version": "1.0",
"data": {
"carrier_key": "dhl",
"carrier_name": "DHL",
"tracking_code": "00340434161094015902",
"tracking_url": "https://www.dhl.de/...",
"label": {
"format": "A6",
"documents": {
"pdf": "BASE64_ENCODED_PDF"
}
}
}
}
Error Response:
{
"version": "1.0",
"error_messages": [
"Recipient country not allowed for this product",
"Invalid postal code format"
]
}
Important Notes
- References: Uses Picqer's custom reference text or order ID for DHL labels. Picklist information is stored but NOT sent to DHL. Reference text is sent to
refNofield which appears on the label - Country Codes: Accepts both 2-letter (DE) and 3-letter (DEU) ISO codes, automatically converts to DHL's required 3-letter format
- Weight: Should be in grams
- Dimensions: Should be in centimeters (automatically converted to millimeters for DHL)
- Processing Time: Responses within 20 seconds (Picqer requirement)
- Label Format: A6 for standard shipments, A4 for customs shipments
- Carrier Key: Returns "dhl" for marketplace/webshop integrations
- Profile Data: Profile configuration from Picqer is used internally but NOT passed to DHL API
Testing the Integration
Test the configuration endpoint:
curl http://localhost:8000/api/webhook/picqer/config-options
Test shipment creation with Picqer format:
curl -X POST http://localhost:8000/api/webhook/picqer/shipment \
-H "Content-Type: application/json" \
-d '{
"version": "1.0",
"data": {
"profile": [
{"key": "shipping_method", "value": "V01PAK"}
],
"shipment": {
"weight": 1500,
"recipient": {
"name": "Test Recipient",
"address": "Teststraße",
"address2": "10",
"zipcode": "10115",
"city": "Berlin",
"country": "DE",
"emailaddress": "[email protected]"
},
"references": {
"reference": "TEST-001",
"orderid": "O2025-TEST"
},
"packaging": {
"length": 30,
"width": 20,
"height": 15
}
}
}
}'
DHL API Integration
Authentication
The middleware uses OAuth2 Password Grant (ROPC) authentication with the DHL API:
- Requires 4 credentials: client_id, client_secret, username (GKP), password (GKP)
- Tokens are cached for 30 minutes (1800 seconds)
- Automatic token refresh on expiration
- Separate credentials for sandbox and production
- Test connection:
php artisan dhl:test-connection
API URLs
Sandbox:
- Auth:
https://api-sandbox.dhl.com/parcel/de/account/auth/ropc/v1/token - API:
https://api-sandbox.dhl.com/parcel/de/shipping/v2
Production:
- Auth:
https://api-eu.dhl.com/parcel/de/account/auth/ropc/v1/token - API:
https://api-eu.dhl.com/parcel/de/shipping/v2
Supported DHL Products
- V01PAK (DHL Paket) - Default
- Additional products can be configured via the
product_codefield
Label Formats
The label format can be configured in config/dhl.php. Supported formats:
| Format Code | Description | Use Case |
|---|---|---|
910-300-410 |
A6 format (105x148mm) | Default - Standard shipping labels |
910-300-700 |
A4 format, 1 label per page | Single label printing |
910-300-710 |
A4 format, 4 labels per page | Batch printing (4-up) |
910-300-600 |
105x205mm format | Extended shipping label |
910-300-300 |
103x199mm format | Alternative label size |
910-300-700-oZ |
A4 without additional docs | Clean A4 format |
To change the label format, edit config/dhl.php:
'label' => [
'format' => '910-300-700', // Change to desired format
],
API Documentation
Environment Modes
Sandbox Mode
Set DHL_MODE=sandbox in .env to use the DHL sandbox environment for testing.
Test your connection:
php artisan dhl:test-connection
Sandbox API URLs:
- Auth:
https://api-sandbox.dhl.com/parcel/de/account/auth/ropc/v1/token - API:
https://api-sandbox.dhl.com/parcel/de/shipping/v2
Production Mode
Set DHL_MODE=production in .env to use the live DHL API.
Production API URLs:
- Auth:
https://api-eu.dhl.com/parcel/de/account/auth/ropc/v1/token - API:
https://api-eu.dhl.com/parcel/de/shipping/v2
Database Schema
Shipments Table
Stores all shipment information:
id- Primary keypicqer_order_id- Reference to Picqer orderpicqer_reference- Order referencepicqer_data- Original Picqer webhook data (JSON)dhl_shipment_number- DHL shipment numberdhl_tracking_number- DHL tracking numberdhl_product- DHL product codedhl_request- DHL API request payload (JSON)dhl_response- DHL API response (JSON)label_url- URL to label (if provided)label_data- Binary label datalabel_format- Label format (PDF, ZPL)status- Shipment status (pending, processing, created, failed, cancelled)error_message- Error message if failedmode- Environment mode (sandbox/production)shipped_at- Timestamp when shippedcreated_at,updated_at,deleted_at
Logging
All operations are logged in storage/logs/laravel.log:
- Webhook receipts
- DHL API calls
- Token generation
- Shipment creation
- Errors and exceptions
Security
- Webhook signature verification (HMAC-SHA256)
- Environment-based configuration
- Secure credential storage
- API request logging
- Error masking in production
Testing
Test DHL Connection
Run the test command to verify your DHL credentials:
php artisan dhl:test-connection
Expected output:
Testing DHL API Connection...
================================
✓ Successfully obtained access token
✓ Token expires in: 1799 seconds
Connection test successful!
Test API Endpoints
Health Check:
curl http://localhost:8000/api/health
Configuration Options (Picqer):
curl http://localhost:8000/api/webhook/picqer/config-options
Create Shipment (Picqer Format):
curl -X POST http://localhost:8000/api/webhook/picqer/shipment \
-H "Content-Type: application/json" \
-d '{
"version": "1.0",
"data": {
"profile": [
{"key": "shipping_method", "value": "V01PAK"}
],
"shipment": {
"weight": 1500,
"recipient": {
"name": "Test Recipient",
"address": "Teststraße",
"address2": "10",
"zipcode": "10115",
"city": "Berlin",
"country": "DE",
"emailaddress": "[email protected]"
},
"references": {
"reference": "TEST-001",
"orderid": "O2025-TEST"
},
"packaging": {
"length": 30,
"width": 20,
"height": 15
}
}
}
}'
List Shipments:
curl http://localhost:8000/api/shipments
Download Label:
# Get the shipment ID from the response, then:
curl http://localhost:8000/api/shipments/{shipment_id}/label > label.pdf
Troubleshooting
Common Issues
-
JWT Token Errors
- Verify all 4 credentials in
.env:- Client ID and Secret (from developer.dhl.com)
- Username and Password (from GKP/Business Portal)
- Check if credentials are for correct environment (sandbox/production)
- Run
php artisan dhl:test-connectionfor detailed diagnostics - Clear cached token:
php artisan cache:clear
- Verify all 4 credentials in
-
Webhook Signature Verification Fails
- Ensure
PICQER_WEBHOOK_SECRETmatches Picqer configuration - Set
PICQER_VERIFY_SIGNATURE=falsefor testing (not recommended for production)
- Ensure
-
Billing Number Not Available
- Sandbox: Use
DHL_BILLING_NUMBER=33333333330101 - Production: Use your actual DHL billing number
- Verify the number matches your environment mode
- Sandbox: Use
-
Label Not Available
- Labels are automatically extracted from DHL's
label.b64field - Stored as base64-decoded PDF in database
- Check
dhl_responsefield in database for raw API response - Verify shipper details are complete in
.env
- Labels are automatically extracted from DHL's
-
Country Code Errors
- Middleware automatically converts 2-letter to 3-letter codes
- Supported: All major European countries (DE→DEU, NL→NLD, etc.)
- If you see "must be three characters", check the country mapping in
DHLShippingService.php
Project Structure
├── app/
│ ├── Http/Controllers/Api/
│ │ └── ShippingWebhookController.php # Main webhook controller
│ ├── Models/
│ │ └── Shipment.php # Shipment model
│ └── Services/DHL/
│ ├── DHLAuthService.php # DHL authentication
│ └── DHLShippingService.php # DHL shipment creation
├── config/
│ ├── dhl.php # DHL configuration
│ └── picqer.php # Picqer configuration
├── database/migrations/
│ └── *_create_shipments_table.php # Shipments table migration
└── routes/
└── api.php # API routes
License
This project is proprietary software.
Support
For issues or questions:
- Check logs in
storage/logs/laravel.log - Review DHL API documentation
- Review Picqer API documentation