DHL delivery service integration
DHL is global logistics operator, key choice for international shipping. API DHL Express (urgent shipments) and DHL eCommerce (e-commerce) are different products with different endpoints. For Russian e-commerce with international shipments, DHL Express API is common.
Authorization
DHL Express uses Basic Auth with API key and secret:
class DhlExpressClient
{
private const BASE_URL = 'https://express.api.dhl.com/mydhlapi';
public function __construct(
private string $apiKey,
private string $apiSecret,
private bool $sandbox = false
) {}
public function request(string $method, string $path, array $params = []): array
{
$url = ($this->sandbox ? 'https://express.api.dhl.com/mydhlapi/test' : self::BASE_URL) . $path;
$response = Http::withBasicAuth($this->apiKey, $this->apiSecret)
->{strtolower($method)}($url, $params);
if ($response->clientError()) {
throw new DhlApiException($response->json()['detail'] ?? 'DHL error');
}
return $response->json();
}
}
Sandbox: apiKey = demo-key, apiSecret = demo-secret.
Rates and delivery times
public function getRates(
array $from,
array $to,
float $weightKg,
string $plannedShipDate
): array {
$response = $this->request('GET', '/rates', [
'originCountryCode' => $from['countryCode'],
'originCityName' => $from['cityName'],
'destinationCountryCode' => $to['countryCode'],
'destinationCityName' => $to['cityName'],
'weight' => $weightKg,
'plannedShippingDateAndTime'=> $plannedShipDate . 'T10:00:00',
]);
return collect($response['products'] ?? [])
->map(fn($p) => [
'product_code' => $p['productCode'],
'product_name' => $p['productName'],
'total_price' => $p['totalPrice'][0]['price'],
'delivery_time' => $p['deliveryCapabilities']['deliveryTypeCode'],
])
->toArray();
}
Product codes: P (DHL Express Worldwide), K (DHL Express 9:00), T (DHL Express 12:00).
Creating shipment
public function createShipment(Order $order): array
{
$payload = [
'plannedShippingDateAndTime' => now()->addDay()->format('Y-m-d') . 'T10:00:00',
'productCode' => $order->dhl_product_code ?? 'P',
'accounts' => [['number' => config('services.dhl.account_number'), 'typeCode' => 'shipper']],
'customerDetails' => [
'shipperDetails' => [
'postalAddress' => [
'cityName' => config('services.dhl.shipper_city'),
'countryCode' => 'RU',
'addressLine1'=> config('services.dhl.shipper_address'),
],
],
'receiverDetails' => [
'postalAddress' => [
'cityName' => $order->shipping_city,
'countryCode' => $order->shipping_country_code,
'addressLine1'=> $order->shipping_address,
],
'contactInformation' => [
'fullName' => $order->recipient_name,
'phone' => $order->recipient_phone,
],
],
],
'content' => [
'packages' => [[
'weight' => $order->total_weight_kg,
'dimensions' => [
'length' => $order->package_length,
'width' => $order->package_width,
'height' => $order->package_height,
],
]],
'isCustomsDeclarable' => $order->is_international,
],
];
$response = $this->request('POST', '/shipments', $payload);
return [
'shipment_id' => $response['shipmentTrackingNumber'],
'label_pdf' => base64_decode($response['documents'][0]['content'] ?? ''),
];
}
Customs declaration
For international shipments:
private function buildExportDeclaration(Order $order): array
{
return [
'lineItems' => $order->items->map(fn($item) => [
'description' => $item->product->name_en,
'price' => $item->price,
'quantity' => ['value' => $item->quantity],
'manufacturerCountry' => 'CN',
])->toArray(),
'invoice' => [
'number' => 'INV-' . $order->id,
'date' => now()->format('Y-m-d'),
],
];
}
Tracking
public function trackShipment(string $trackingNumber): array
{
$response = $this->request('GET', '/tracking', ['trackingNumber' => $trackingNumber]);
$shipment = $response['shipments'][0] ?? null;
return [
'status' => $shipment['status'] ?? '',
'location' => $shipment['location']['address']['cityName'] ?? '',
'events' => collect($shipment['events'] ?? [])->map(fn($e) => [
'timestamp' => $e['timestamp'],
'description' => $e['description'],
])->toArray(),
];
}
Scheduling pickup
public function schedulePickup(string $date, string $timeFrom, int $parcelCount): string
{
$response = $this->request('POST', '/pickups', [
'plannedPickupDateAndTime' => $date . 'T' . $timeFrom . ':00',
'accounts' => [['number' => config('services.dhl.account_number')]],
]);
return $response['dispatchConfirmationNumber'];
}
Limitations
DHL strictly validates recipient addresses. Max weight 70 kg, max side 300 cm. Mandatory for some countries: state/province field.
Timeline
Rate calculation + shipment creation + tracking — 5–7 days. Adding customs declarations — 2–3 more days. Sandbox testing mandatory.







