Подключаем DHL Express API: расчёт, отправление и трекинг
Ошибка 401 при попытке получить тарифы DHL — типичная ситуация. Разработчик тратит два дня на отладку, а проблема в неверном продукте: DHL Express и DHL eCommerce используют разные механизмы авторизации. Штатный программист неделями изучает документацию, но всё равно спотыкается о неочевидные ограничения — максимальный вес 70 кг, обязательная таможенная декларация для международных отправлений, строгая валидация адресов. Мы накопили опыт интеграции DHL Express API на 30+ проектах, от простого расчёта до полного цикла создания отправления с трекингом. Результат: прозрачная доставка, минимум ошибок, довольные клиенты. Закажите такую интеграцию — это сэкономит время и деньги.
Решаемые проблемы
- Авторизация и различия API: DHL Express (Basic Auth) и DHL eCommerce (OAuth 2.0) — разные продукты. Использование неправильного API ведёт к ошибкам 401 и неверным тарифам. Мы выбираем нужный API и настраиваем Basic Auth.
- Ошибки адресов и таможни: неверный почтовый индекс или отсутствие таможенной декларации — частые причины отказов. Мы валидируем адреса через Google Maps API и автоматизируем заполнение декларации с HS-кодами.
- Обработка ошибок: DHL возвращает детальные ошибки, но их нужно корректно обрабатывать на стороне сайта. Наша реализация выбрасывает исключения с понятными сообщениями, что сокращает время отладки на 50%.
Как мы интегрируем
Используем паттерн Repository для изоляции DHL API. Все запросы проходят через единый клиент, который обрабатывает авторизацию и ошибки. Для одного мультибрендового магазина электроники с отправками в 20 стран мы интегрировали DHL Express API, добавили таможенные декларации с автозаполнением HS-кодов. Результат: время обработки заказа сократилось на 40%, количество ошибок при создании отправлений — на 70%.
Сравнение продуктов DHL Express и DHL eCommerce
| Параметр | DHL Express | DHL eCommerce |
|---|---|---|
| Тип авторизации | Basic Auth (API Key/Secret) | OAuth 2.0 (Client ID/Secret) |
| Назначение | Экспресс-доставка (1-3 дня) | Экономичная доставка (5-10 дней) |
| Таможня | Обязательна для международных | Не всегда |
| Трекинг | Полный, с событиями | Ограниченный |
Коды продуктов DHL Express
| Код | Продукт | Особенности |
|---|---|---|
| P | DHL Express Worldwide | Основное международное |
| K | DHL Express 9:00 | Доставка к 9 утра |
| T | DHL Express 12:00 | Доставка к полудню |
| Y | DHL Express Envelope | Документы в конверте |
Техническая реализация
Авторизация
DHL Express API использует Basic Auth с API key и API 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
) {
if ($sandbox) {
// Sandbox: другой URL
// https://express.api.dhl.com/mydhlapi/test
}
}
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)
->withHeaders(['Content-Type' => 'application/json'])
->{strtolower($method)}($url, $params);
if ($response->clientError()) {
$error = $response->json();
throw new DhlApiException(
$error['detail'] ?? $error['title'] ?? 'DHL API error',
$response->status()
);
}
return $response->json();
}
}
Sandbox credentials: apiKey = demo-key, apiSecret = demo-secret — для тестирования. Реальные ключи получают в DHL Developer Portal.
Расчёт стоимости и сроков
public function getRates(
array $from, // ['countryCode'=>'RU','cityName'=>'Moscow','postalCode'=>'101000']
array $to, // ['countryCode'=>'DE','cityName'=>'Berlin','postalCode'=>'10115']
float $weightKg,
array $dimensions,
string $plannedShipDate
): array {
$data = $this->request('GET', '/rates', [
'accountNumber' => config('services.dhl.account_number'),
'originCountryCode' => $from['countryCode'],
'originCityName' => $from['cityName'],
'originPostalCode' => $from['postalCode'],
'destinationCountryCode' => $to['countryCode'],
'destinationCityName' => $to['cityName'],
'destinationPostalCode' => $to['postalCode'],
'weight' => $weightKg,
'length' => $dimensions['length'],
'width' => $dimensions['width'],
'height' => $dimensions['height'],
'plannedShippingDateAndTime' => $plannedShipDate . 'T10:00:00 GMT+03:00',
'isCustomsDeclarable' => true,
'unitOfMeasurement' => 'metric',
]);
return collect($data['products'] ?? [])
->map(fn($p) => [
'product_code' => $p['productCode'],
'product_name' => $p['productName'],
'currency' => $p['totalPrice'][0]['priceCurrency'],
'total_price' => $p['totalPrice'][0]['price'],
'delivery_time'=> $p['deliveryCapabilities']['deliveryTypeCode'],
'delivery_date'=> $p['deliveryCapabilities']['estimatedDeliveryDateAndTime'] ?? null,
])
->toArray();
}
Создание отправления
public function createShipment(Order $order): array
{
$payload = [
'plannedShippingDateAndTime' => now()->addDay()->format('Y-m-d') . 'T10:00:00 GMT+03:00',
'pickup' => [
'isRequested' => false, // false = самостоятельная сдача на склад DHL
],
'productCode' => $order->dhl_product_code ?? 'P',
'accounts' => [
['number' => config('services.dhl.account_number'), 'typeCode' => 'shipper'],
],
'customerDetails' => [
'shipperDetails' => [
'postalAddress' => [
'postalCode' => config('services.dhl.shipper_zip'),
'cityName' => config('services.dhl.shipper_city'),
'countryCode' => 'RU',
'addressLine1'=> config('services.dhl.shipper_address'),
],
'contactInformation' => [
'email' => config('services.dhl.contact_email'),
'phone' => config('services.dhl.contact_phone'),
'companyName' => config('services.dhl.company_name'),
'fullName' => config('services.dhl.contact_name'),
],
],
'receiverDetails' => [
'postalAddress' => [
'postalCode' => $order->shipping_zip,
'cityName' => $order->shipping_city,
'countryCode' => $order->shipping_country_code,
'addressLine1'=> $order->shipping_address,
],
'contactInformation' => [
'email' => $order->recipient_email,
'phone' => $order->recipient_phone,
'fullName' => $order->recipient_name,
],
],
],
'content' => [
'packages' => [[
'weight' => $order->total_weight_kg,
'dimensions' => [
'length' => $order->package_length,
'width' => $order->package_width,
'height' => $order->package_height,
],
]],
'isCustomsDeclarable' => $order->is_international,
'description' => 'E-commerce goods',
'incoterm' => 'DAP',
'unitOfMeasurement' => 'metric',
// Таможенная декларация для международных отправлений
'exportDeclaration' => $order->is_international ? $this->buildExportDeclaration($order) : null,
],
];
$response = $this->request('POST', '/shipments', $payload);
return [
'shipment_id' => $response['shipmentTrackingNumber'],
'shipment_number' => $response['shipmentDetails'][0]['shipmentTrackingNumber'],
'label_pdf' => base64_decode($response['documents'][0]['content'] ?? ''),
];
}
Таможенная декларация
Для международных отправлений обязательна:
private function buildExportDeclaration(Order $order): array
{
return [
'lineItems' => $order->items->map(fn($item, $i) => [
'number' => $i + 1,
'description' => $item->product->name_en, // на английском
'price' => $item->price,
'priceCurrency' => 'USD',
'grossWeight' => [
'weight' => $item->product->weight_kg,
'unitOfMeasurement' => 'kg',
],
'quantity' => [
'value' => $item->quantity,
'unitOfMeasurement' => 'PCS',
],
'manufacturerCountry' => 'CN',
'hsCode' => $item->product->hs_code ?? '6109100000',
])->toArray(),
'invoice' => [
'number' => 'INV-' . $order->id,
'date' => now()->format('Y-m-d'),
'signedBy' => config('services.dhl.contact_name'),
'function' => 'Seller',
'customerReference' => (string)$order->id,
],
'exportReason' => 'PERMANENT',
'exportReasonType'=> 'PERMANENT',
'placeOfIncoterm' => 'Destination',
'shipmentType' => 'commercial',
];
}
Отслеживание
public function trackShipment(string $trackingNumber): array
{
$response = $this->request('GET', '/tracking', [
'trackingNumber' => $trackingNumber,
]);
$shipment = $response['shipments'][0] ?? null;
if (!$shipment) {
return [];
}
return [
'status' => $shipment['status'],
'description' => $shipment['description'],
'location' => $shipment['location']['address']['cityName'] ?? '',
'events' => collect($shipment['events'])->map(fn($e) => [
'timestamp' => $e['timestamp'],
'location' => $e['location']['address']['cityName'] ?? '',
'description'=> $e['description'],
])->toArray(),
'estimated_delivery' => $shipment['estimatedTimeOfDelivery'] ?? null,
];
}
Ограничения и типичные ошибки
DHL строго проверяет адреса получателей. Неточный почтовый индекс вернёт ошибку. Максимальный вес одного места — 70 кг, размер стороны — 300 см. Типичная ошибка — неверный учётный номер. Мы валидируем адреса через Google Maps API перед отправкой. В sandbox обязательно тестируйте каждую функцию.
Как автоматизировать таможенную декларацию?
Таможенная декларация обязательна для всех международных отправлений DHL Express. Мы автоматизируем её заполнение: HS-коды подтягиваются из базы товаров, описание и стоимость формируются на основе заказа. Это исключает ручной ввод и снижает риск ошибок. В sandbox проверьте заполнение декларации перед продакшном.
Что входит в работу?
- Документация по интеграции API.
- Ключи доступа к sandbox и продакшну.
- Обучение операторов работе с заказами.
- Техническая поддержка 3 месяца после деплоя.
- Гарантия корректной работы всех функций.
Процесс работы
- Аналитика: изучаем ассортимент, направления, объём заказов.
- Проектирование: выбираем продукт DHL, схему отправлений.
- Реализация: интеграция API, настройка таможенных деклараций.
- Тестирование: в sandbox с реальными ключами.
- Деплой: развёртывание на боевом стенде.
Сроки
Интеграция DHL Express для интернет-магазина — от 5 до 7 рабочих дней. Дополнительные настройки таможни — ещё 2–3 дня.
Как избежать ошибок авторизации?
Проверьте, что используете правильный API: DHL Express (Basic Auth) или DHL eCommerce (OAuth 2.0). Убедитесь, что в запросе есть корректные apiKey и apiSecret. В sandbox используйте demo-key и demo-secret. Для продакшена – ключи из аккаунта DHL Developer.
Свяжитесь с нами, чтобы обсудить интеграцию DHL API под ваш проект. Мы гарантируем корректную работу и предоставляем поддержку. Закажите интеграцию уже сегодня.







