DHL is the carrier an Indian D2C brand meets most often on the international lane, and through Blue Dart it is also present in the Indian domestic market. For a commerce database DHL is a carrier connector, not a sales channel: no orders, no listings, no stock. The important thing to understand before writing any code is that "the DHL API" does not exist. DHL Group is a federation of divisions and each one has its own API with its own host, its own credential type and its own field naming. There are three surfaces that matter to us, and this page treats them separately: the MyDHL API for DHL Express, the DHL eCommerce Solutions API, and the Shipment Tracking Unified API that reads across every division at once.
At a glance
What it is
DHL Group, formerly Deutsche Post DHL, is a German listed logistics group. The divisions that issue APIs are DHL Express (time definite international air express, the premium product), DHL eCommerce (volume parcel, domestic and cross border), Post and Parcel Germany, DHL Freight (road LTL and FTL in Europe), DHL Global Forwarding (air and ocean freight) and DHL Supply Chain (contract logistics). The unified APIs on the developer portal are the group's attempt to put one front door on all of them.
In India, DHL Express operates the international express lane, and DHL Group holds the majority stake in Blue Dart Express, the largest Indian domestic air express operator. That matters for our data model: a shipment that looks like a DHL shipment to a seller may be carried and tracked by Blue Dart end to end, with a Blue Dart waybill number and Blue Dart's own API, which is a separate connector. The unified Shipment Tracking API's service parameter has no bluedart value, so do not expect it to resolve a Blue Dart waybill.
As with every carrier here there is no marketplace and no catalogue. One connection is one DHL account number per division, and a brand shipping both Express international and eCommerce domestic parcels is two connections.
API access
Three routes, and they are genuinely different products.
Shipment Tracking Unified API, and the other unified APIs
Fully self serve. Register on developer.dhl.com, create an app in the MyApps dashboard, subscribe the app to the Shipment Tracking API, and the portal issues a Consumer Key. That key is the value of the DHL-API-Key header. No account number is needed to start, and no DHL sales contact. The same mechanism covers the Location Finder Unified API and the Shipment Tracking Unified Push API.
DHL states on the API reference that initial access grants 250 calls per day, with a maximum of one call every five seconds, and that more capacity requires an upgrade request through the portal.
MyDHL API, for DHL Express
Not self serve. The MyDHL API reference states that access credentials are provided by a DHL Express consultant, and that an active DHL Express customer account is required. The legal terms attached to the API restrict sharing Product and Rating data with competitors. The test environment gives 500 service invocations a day against the credentials issued.
DHL eCommerce Solutions
Not self serve either. A DHL eCommerce account is required and the account manager issues a client id and client secret. The API reference material lives at docs.api.dhlecs.com behind the portal. The API covers products, duty and tax, label generation, manifesting, pickup and tracking, and returns.
developer.dhl.com pages for the unified APIs rendered for an unauthenticated fetcher on 2026-09-21 and are cited directly. The MyDHL API and DHL eCommerce Solutions reference pages did not: the detailed operation pages sit behind the portal login and developer.dhl.com/api-reference/dhl-ecommerce-solutions returned 404 to the fetcher. Endpoint paths and sample bodies for those two families on this page therefore come from working open source clients that call the live services, named in Sources, and are marked medium confidence. Re-verify them against the portal reference before writing code.
Authentication
Shipment Tracking Unified API: static key
One header, no token exchange, no expiry to manage.
curl -X GET \ "https://api-eu.dhl.com/track/shipments?trackingNumber=7777777770&service=express&language=en" \ -H "DHL-API-Key: YOUR_CONSUMER_KEY"
The security scheme in the official specification is exactly {"type": "apiKey", "name": "DHL-API-Key", "in": "header"}, described as the "Consumer Key from DHL Developer Portal MyApps dashboard". Hosts are https://api-eu.dhl.com/track for production and https://api-test.dhl.com/track for test.
Because the key is static and per app rather than per merchant, the quota is shared across every merchant we track. Plan the quota as an app level budget, not a per customer one.
MyDHL API: HTTP Basic
The MyDHL API reference states that "the Authorization header as part of the request is set as pre-emptively and following the BasicAuth standards". That means: base64 encode username:password and send it on the first request without waiting for a 401 challenge.
curl -X GET \ "https://express.api.dhl.com/mydhlapi/test/tracking?shipmentTrackingNumber=1234567890" \ -u "MYDHL_USERNAME:MYDHL_PASSWORD" \ -H "Content-Type: application/json"
The account number travels in the request body rather than in the credential: rate and shipment requests carry an accounts[] array of { typeCode, number } where typeCode is shipper or payer. One set of Basic credentials can therefore serve several DHL Express account numbers, which is the multi account model here.
DHL eCommerce Solutions: client credentials
A token exchange against /auth/v4/accesstoken, then a bearer token on every call. The reference client sends the credentials form encoded:
curl -X POST "https://api-sandbox.dhlecs.com/auth/v4/accesstoken" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=client_credentials" \ -d "client_id=YOUR_CLIENT_ID" \ -d "client_secret=YOUR_CLIENT_SECRET"
The response carries access_token and expires_in. The reference client caches the token for half of expires_in and re-mints, which is a sensible default given the lifetime is not published outside the portal.
Objects we can read
DHL is a carrier. Of the unified model objects only shipments is natively present, with returns partially present through return labels. There are no orders, order items, products, listings, inventory, customers or settlements.
Orders
Not available. Order context reaches DHL only as references. On MyDHL API these are customerReferences[] on the shipment and on each package; on the unified Track response they come back as details.references[], an array of { "@scope", "number", "type" } where type is one of container-number, customer-confirmation-number, customer-reference, domestic-consignment-id, ecommerce-number, housebill, local-tracking-number, masterbill, payer-account-number, receiver-account-number, reference, shipment-id or shipper-account-number. Write our order_id in as customer-reference at ship time or the link is lost.
Shipments and tracking, unified
GET https://api-eu.dhl.com/track/shipments is the one endpoint that reads across every DHL division. One tracking number per call.
Always send service when we know it. Without it DHL has to guess which division owns the number, and an ambiguous number can resolve to more than one shipment, which is exactly what offset and limit exist for.
Response, trimmed to one shipment and one event, using field names from the official DHL specification. Addresses and names here are PII, and DHL masks them by policy: note the @policy markers on addresses and person entities, which say how much of the value the requester is allowed to see.
{
"url": "/track/shipments?trackingNumber=7777777770",
"nextUrl": null,
"shipments": [
{
"id": "7777777770",
"service": "express",
"origin": {
"address": {
"@policy": "postal-code",
"addressLocality": "Prague",
"addressRegion": "Prague 11",
"countryCode": "CZ",
"postalCode": "14000",
"streetAddress": "Batman Avenue 1040"
}
},
"destination": {
"address": { "@policy": "postal-code", "addressLocality": "New York", "countryCode": "US", "postalCode": "10001" }
},
"status": {
"timestamp": "2026-09-18T14:22:00",
"statusCode": "delivered",
"status": "DELIVERED",
"description": "Delivered - Signed for by",
"remark": null,
"nextSteps": null,
"location": { "address": { "addressLocality": "New York", "countryCode": "US" } }
},
"pickUpDate": "2026-09-15T10:00:00",
"estimatedTimeOfDelivery": "2026-09-18T18:00:00",
"estimatedDeliveryTimeFrame": { "estimatedFrom": "2026-09-18T12:00:00", "estimatedThrough": "2026-09-18T18:00:00" },
"estimatedTimeOfDeliveryRemark": null,
"serviceUrl": null,
"rerouteUrl": null,
"returnFlag": false,
"details": {
"product": { "productCode": "P", "productName": "Worldwide Priority", "deliveryMethodRemark": "D2P" },
"provider": { "destinationProvider": "acs-courier" },
"sender": { "@type": "Organization", "organizationName": "Acme Brands" },
"receiver": { "@type": "Person", "givenName": "Jane", "familyName": "Smith" },
"proofOfDelivery": {
"timestamp": "2026-09-18T14:22:00",
"documentUrl": "https://webpod.dhl.com/pod?token=510f1359603a768a57af49cf10083f90&language=en",
"signatureUrl": null
},
"proofOfDeliverySignedAvailable": true,
"totalNumberOfPieces": 1,
"pieceIds": ["JD014600001234567890"],
"weight": { "value": 1.5, "unitText": "kg" },
"dimensions": {
"length": { "value": 0.15, "unitText": "m" },
"width": { "value": 0.15, "unitText": "m" },
"height": { "value": 0.15, "unitText": "m" }
},
"references": [ { "@scope": "public", "type": "customer-reference", "number": "ORDER-20483739" } ]
},
"events": [
{
"timestamp": "2026-09-18T14:22:00",
"statusCode": "delivered",
"status": "OK",
"description": "Delivered - Signed for by",
"remark": null,
"nextSteps": null,
"pieceIds": ["JD014600001234567890"],
"location": { "address": { "addressLocality": "New York", "countryCode": "US" } }
}
]
}
]
}
Notes for the ingest:
status.statusCodeis a five value grouping:pre-transit,transit,delivered,failure,unknown. That is the whole vocabulary on the unified API, and it is deliberately coarse. The finer detail is instatus.statusandstatus.description, which are free text and localised bylanguage. So: map our coarseshipments.statusfromstatusCode, and keep the raw text for the event history rather than trying to parse it.failureis the only exception signal on this API. There is no structured NDR reason code here. If we need reason codes we have to read the division specific API instead, which for Express is the MyDHL tracking endpoint and itstypeCodevalues.events[].pieceIdslinks an event to a specific piece of a multi piece shipment. Keep it, or a multi piece delivery collapses into one ambiguous event stream.details.proofOfDelivery.documentUrlis a tokenised link rather than an inline document, andproofOfDeliverySignedAvailabletells you whether a signed version exists before you follow it.@policyon addresses and@policyon person entities tell you DHL has masked or partially masked the value. Do not treat a missing street line as "the sender had no street"; treat it as "we were not entitled to it on this key".- Errors follow RFC 7807 problem detail:
{ "type", "title", "status", "detail", "instance" }with HTTP400,401,404or429.
Shipments and tracking, DHL Express
GET /mydhlapi/tracking?shipmentTrackingNumber=... returns the Express specific view, which is richer than the unified one on piece level detail and on the signature.
{
"shipments": [
{
"shipmentTrackingNumber": "1234567890",
"status": "delivered",
"shipmentTimestamp": "2024-11-05T10:00:00",
"productCode": "P",
"description": "EXPRESS WORLDWIDE",
"shipperDetails": {
"name": "Shipper Name",
"postalAddress": { "cityName": "Prague", "postalCode": "14800", "countryCode": "CZ" },
"serviceArea": [ { "code": "PRG", "description": "Prague", "outboundSortCode": "PRG01" } ]
},
"receiverDetails": {
"name": "Receiver Name",
"postalAddress": { "cityName": "New York", "postalCode": "10001", "provinceCode": "NY", "countryCode": "US" },
"serviceArea": [ { "code": "NYC", "facilityCode": "JFK", "inboundSortCode": "NYC01" } ]
},
"totalWeight": 5.0,
"unitOfMeasurements": "metric",
"numberOfPieces": 1,
"shipperReferences": [ { "value": "REF-001", "typeCode": "CU" } ],
"estimatedTimeOfDelivery": "2024-11-07T18:00:00",
"estimatedTimeOfDeliveryRemark": "Estimated delivery by end of business",
"events": [
{
"date": "2024-11-07",
"time": "18:00:00",
"GMTOffset": "+00:00",
"typeCode": "OK",
"description": "Delivered",
"signedBy": "J. Smith",
"serviceArea": [ { "code": "NYC", "description": "New York" } ]
}
],
"pieces": [
{
"number": 1,
"typeCode": "2BP",
"shipmentTrackingNumber": "1234567890",
"trackingNumber": "JD014600001234567890",
"weight": 5.0,
"dimensionalWeight": 3.38,
"actualWeight": 5.0,
"dimensions": { "length": 15, "width": 15, "height": 15 },
"events": [ { "date": "2024-11-07", "time": "18:00:00", "typeCode": "OK", "description": "Delivered" } ]
}
]
}
]
}
Note the difference in time handling between the two APIs: the unified API gives a single ISO 8601 timestamp, MyDHL gives date, time and GMTOffset as three separate strings that you have to recombine. typeCode on a MyDHL event is the Express scan code, OK for delivered, and is the closest thing to a structured exception reason in this family.
Proof of delivery
Two routes:
- Unified:
details.proofOfDelivery.documentUrlandsignatureUrlon the track response, gated byproofOfDeliverySignedAvailable. - MyDHL:
GET /mydhlapi/shipments/{shipmentTrackingNumber}/proof-of-delivery, which returns the electronic proof of delivery document for an Express shipment. The relatedGET /mydhlapi/shipments/{shipmentTrackingNumber}/get-imageretrieves other shipment documents such as the waybill and customs paperwork.
Rates, serviceability and transit time, DHL Express
POST /mydhlapi/rates returns products, prices and, in the same response, the pickup cutoff and the estimated delivery date. There is a lighter POST /mydhlapi/products for single piece product availability and a POST /mydhlapi/landed-cost for duty and tax.
Request, from a working client:
{
"customerDetails": {
"shipperDetails": { "postalCode": "14800", "cityName": "Prague", "countryCode": "CZ", "addressLine1": "V Parku 2308/10" },
"receiverDetails": { "postalCode": "10001", "cityName": "New York", "countryCode": "US", "provinceCode": "NY", "addressLine1": "123 Main Street" }
},
"accounts": [ { "typeCode": "shipper", "number": "ACC123456789" } ],
"productsAndServices": [
{ "productCode": "P", "localProductCode": "P", "valueAddedServices": [ { "serviceCode": "II", "value": 100.00, "currency": "USD" } ] }
],
"payerCountryCode": "US",
"plannedShippingDateAndTime": "2024-11-05T14:00:00 GMT+00:00",
"unitOfMeasurement": "metric",
"isCustomsDeclarable": true,
"monetaryAmount": [ { "typeCode": "declaredValue", "value": 100.00, "currency": "USD" } ],
"estimatedDeliveryDate": { "isRequested": true, "typeCode": "QDDC" },
"packages": [ { "typeCode": "2BP", "weight": 5.0, "dimensions": { "length": 15, "width": 15, "height": 15 } } ]
}
Response, trimmed to one product:
{
"products": [
{
"productName": "EXPRESS WORLDWIDE",
"productCode": "P",
"localProductCode": "P",
"localProductCountryCode": "CZ",
"networkTypeCode": "TD",
"isCustomerAgreement": false,
"weight": { "volumetric": 3.38, "provided": 5.0, "unitOfMeasurement": "metric" },
"totalPrice": [ { "currencyType": "BILLC", "priceCurrency": "EUR", "price": 89.25 } ],
"totalPriceBreakdown": [
{ "currencyType": "BILLC", "priceCurrency": "EUR", "priceBreakdown": [ { "typeCode": "SPRQT", "price": 65.5 } ] }
],
"pickupCapabilities": {
"nextBusinessDay": true,
"localCutoffDateAndTime": "2024-11-05T16:00:00",
"GMTCutoffTime": "18:30:00",
"pickupEarliest": "10:00:00",
"pickupLatest": "18:30:00",
"originServiceAreaCode": "BER"
},
"deliveryCapabilities": {
"deliveryTypeCode": "QDDC",
"estimatedDeliveryDateAndTime": "2024-11-07T18:00:00",
"destinationServiceAreaCode": "NYC",
"totalTransitDays": 4
}
}
]
}
totalPrice[] is an array because DHL quotes in several currency views at once: currencyType of BILLC is the billing currency, and there are also base and pricing currency views. Pick the BILLC entry for anything the merchant will actually be invoiced.
There is no separate postal code serviceability endpoint for Express. Serviceability is a rate call that either returns products or does not, plus GET /mydhlapi/address-validate which validates pickup and delivery capability at an origin or destination. The eCommerce family answers the same question with POST /shipping/v4/products.
Pickup
DHL Express pickup, on MyDHL API:
{
"dispatchConfirmationNumbers": ["PRG201220123456"],
"readyByTime": "10:00",
"nextPickupDate": "2024-11-06",
"warnings": ["Pickup created but something may need attention"]
}
dispatchConfirmationNumber is the handle for update and cancel, and the same value comes back on a shipment create response when the shipment booked its own pickup. Store it against the shipment.
Returns and cancellations
Partial. DHL Express models returns as label variants on the shipment request rather than as a return object, and the unified track response exposes a returnFlag boolean plus rerouteUrl when the shipment can be redirected. DHL eCommerce Solutions generates return labels through the same label endpoint. Cancellation on MyDHL is a small request body of { "shipmentTrackingNumber", "reason" }.
There is no NDR object and no RTO object anywhere in this family. On the unified API a failed attempt is statusCode: "failure" with a free text description; on MyDHL it is an event typeCode. Both have to be derived.
Payments and settlements
Not available in these APIs. DHL billing is a separate product. Cash on delivery exists as a value added service on the Express shipment request, and the eCommerce Solutions API covers duty and tax calculation, but there is no COD remittance API here: money actually received has to be reconciled from DHL billing files outside this catalogue.
Customers, products, listings, inventory, locations
Not applicable. The only location concept is DHL's own network: the Location Finder Unified API and GET /mydhlapi/servicepoint find DHL service points and drop off locations, and originServiceAreaCode, destinationServiceAreaCode and facilityCode on a rate or track response identify DHL facilities. None of those are seller warehouses.
Writing back: listings, price and stock
Not applicable, this is a carrier. There are no listings, prices or stock levels at DHL and nothing here writes to a sales channel. The write path is shipment lifecycle, and it differs by division.
DHL Express: create a shipment
POST /mydhlapi/shipments creates the shipment, produces the label and customs documents, and can book the pickup in the same call. The response is where the waybill number and the label bytes arrive:
{
"url": "https://express.api.dhl.com/mydhlapi/shipments",
"shipmentTrackingNumber": "1234567890",
"dispatchConfirmationNumber": "PRG200227000256",
"cancelPickupUrl": "https://express.api.dhl.com/mydhlapi/shipment-pickups/PRG200227000256",
"trackingUrl": "https://express.api.dhl.com/mydhlapi/shipments/1234567890/tracking",
"onDemandDeliveryURL": "https://odd-test.dhl.com/odd-online/US/wH24aaaaa1",
"packages": [
{
"referenceNumber": 1,
"trackingNumber": "JD014600001234567890",
"volumetricWeight": 3.38,
"documents": [ { "imageFormat": "PNG", "content": "base64 encoded qr code", "typeCode": "qr-code" } ]
}
],
"documents": [
{ "imageFormat": "PDF", "content": "JVBERi0xLjQKJeLjz9MK...", "typeCode": "label", "packageReferenceNumber": 1 }
],
"shipmentDetails": [
{
"volumetricWeight": 3.38,
"billingCode": "IMP",
"serviceContentCode": "WPX",
"packagesCount": 1,
"originServiceArea": { "facilityCode": "PRG", "serviceAreaCode": "PRG", "outboundSortCode": "PRG01" },
"destinationServiceArea": { "facilityCode": "JFK", "serviceAreaCode": "NYC", "inboundSortCode": "NYC01" },
"dhlRoutingCode": "CZ>CZ>PRG+51000001 ECXDWW+ D999999C",
"dhlRoutingDataId": "1Z"
}
],
"shipmentCharges": [
{
"currencyType": "BILLC",
"priceCurrency": "USD",
"price": 147.5,
"serviceBreakdown": [ { "name": "EXPRESS WORLDWIDE", "price": 135.0, "typeCode": "FF" } ]
}
],
"warnings": []
}
shipmentTrackingNumber is the shipment level waybill and packages[].trackingNumber the per piece number. Persist both, as on every carrier here. The label is base64 in documents[] where typeCode is label, alongside invoice, waybill-doc and qr-code entries. cancelPickupUrl and trackingUrl are returned ready formed, which is a convenience worth storing rather than reconstructing.
Related write operations:
PATCH /mydhlapi/shipments/{shipmentTrackingNumber}/upload-imageattaches an image to an existing shipment.POST /mydhlapi/upload-invoice-datauploads paperless trade invoice data before or after shipment creation.- Cancel with
{ "shipmentTrackingNumber": "1234567890", "reason": "Customer request" }.
DHL eCommerce Solutions: create a label and manifest
Label request, from a working client:
{
"pickup": "5351244",
"distributionCenter": "USDFW1",
"orderedProductId": "GND",
"consigneeAddress": {
"name": "John Doe",
"address1": "114 Whitney Ave",
"city": "New Haven",
"state": "CT",
"postalCode": "06510",
"country": "US"
},
"returnAddress": {
"companyName": "Mercatalyst",
"address1": "1950 Parker Road",
"address2": "Receiving Door 32",
"city": "Carrollton",
"state": "TX",
"postalCode": "75010",
"country": "US"
},
"packageDetail": {
"packageId": "GM60511234500000001",
"packageDescription": "ORDER NO 20483739DFDR",
"weight": { "value": 3, "unitOfMeasure": "LB" },
"dimension": { "length": 14, "width": 14, "height": 14, "unitOfMeasure": "IN" }
}
}
The manifest step is not optional. In the eCommerce Solutions model a label is not tendered until it is manifested: POST /shipping/v4/manifest takes manifests[].dhlPackageIds[] and produces the Driver's Summary Manifest. A connector that creates labels and never manifests will leave packages that DHL does not expect. Build the close out into the daily job from the start, not as a later fix.
Note the identifier split here: packageId is our id, supplied by us on the request and capped at 30 characters, while dhlPackageId and the carrier trackingId are DHL's. The tracking endpoint takes either, on different query parameters, so store all three.
Webhooks and notifications
The Shipment Tracking Unified Push API is a genuine HTTP webhook and it covers DHL Group, Freight, eCommerce and Global Forwarding. Base URL https://api-eu.dhl.com/, same DHL-API-Key header as the pull API.
The subscription lifecycle has three phases:
- Creation. We create a subscription. DHL immediately calls our webhook with a confirmation carrying a validation secret.
- Activation. We verify the secret. Once verified, the subscription is ready.
- Notification. DHL pushes shipment status updates to our webhook as they happen.
Two subscription types: by individual tracking id, which activates immediately, and by customer account, which requires business approval from DHL first. The account level one is what we want for a merchant shipping at volume, and it needs a conversation with DHL before it will work.
The notification body carries a subscription.push scope and a shipments[] array with the same shape as the pull API's status object:
{
"scope": "subscription.push",
"shipments": [
{
"id": "7777777770",
"service": "express",
"status": {
"timestamp": "2026-09-18T14:22:00",
"statusCode": "transit",
"status": "PROCESSED",
"location": { "address": { "countryCode": "US", "postalCode": "10001", "addressLocality": "New York" } }
}
}
]
}
Receiver requirements published by DHL:
- A valid SSL certificate on the endpoint.
- Reply
200within five seconds. Acknowledge first, process asynchronously. Do not do database work on the request thread. - Handle retries: DHL retries after one hour, then after six hours on subsequent failures. That is a slow retry curve, so a receiver that is down for a few minutes will be waiting an hour for the replay.
Verification is by the validation secret exchanged during activation rather than by an HMAC over each body. The exact header that carries it on subsequent notifications is not stated on the reachable documentation, so confirm it during integration and, until then, treat network level controls and the coarse body shape as the only guards.
Recommended design: take the push stream as primary, de-duplicate on the tuple of id, status.timestamp and status.statusCode, and back it with a low rate GET /track/shipments reconciliation poll for anything that has been silent for more than 48 hours. Given the 250 calls a day starting quota on the pull API, the webhook is not a nice to have here, it is the only way to track any real volume without an upgrade.
Rate limits and pagination
The 250 a day starting quota is the number that shapes the whole design. At one call per tracking number it covers 250 shipments a day with zero re-polls, which is nothing. Three consequences: request a quota upgrade through the portal during onboarding, use the Push API rather than polling, and for DHL Express prefer the MyDHL tracking endpoint, which is governed by the Express account rather than by the unified app quota.
Errors on the unified API are RFC 7807 problem details with type, title, status, detail and instance. MyDHL API returns its own error body and surfaces soft failures as a warnings[] array on an otherwise successful response, so check warnings even on a 200.
Mapping to the unified model
DHL populates shipments and nothing else. Where two APIs give the same field, both are named.
Gaps and open questions
- MyDHL API and DHL eCommerce Solutions reference detail is behind the portal login. Endpoint paths and sample bodies on this page for those two families come from working open source clients. Re-verify every field against the portal reference and the Postman collections before implementation, and treat the request shapes as medium confidence.
- Production quotas for MyDHL API and DHL eCommerce Solutions are not published. Only the test environment number (500 a day for MyDHL) is stated. Ask the DHL Express consultant and the eCommerce account manager for the production numbers.
- Push API verification header is not documented on the reachable pages. We know a validation secret is exchanged at activation, but not the header name or format on subsequent notifications. Confirm during integration.
- Account level push subscriptions need DHL business approval. Find out how long that takes and what it requires before promising a merchant a go live date, because per tracking id subscriptions will not scale against a 250 a day pull quota.
- Blue Dart is a separate connector. DHL Group's Indian domestic arm does not appear in the unified API's
serviceenum. Confirm whether a Blue Dart waybill is resolvable through any DHL endpoint, and if not, build Blue Dart separately. - No COD remittance and no billing API, so
shipping_costis the rated estimate. Decide whether to reconcile from DHL billing files. - The DHL eCommerce Solutions token endpoint method is unconfirmed. The reference client posts form encoded credentials to
/auth/v4/accesstoken; whether HTTP Basic is also accepted, and what the token lifetime is, could not be read from the portal. Verify in the sandbox. - Division fragmentation is a permanent tax. Express, eCommerce, Freight, Global Forwarding and Post and Parcel Germany each have their own write API. The unified API only unifies reads. Budget for one write integration per division a merchant actually uses.
Sources
- developer.dhl.com/api-catalog, the API catalogue listing which division owns which API
- Shipment Tracking Unified API reference, read on 2026-09-21: base URL,
DHL-API-Keyheader, query parameters, the 250 calls a day and one call per five seconds quota,429behaviour - Shipment Tracking Unified Push API reference, read on 2026-09-21: three phase subscription, subscription types,
subscription.pushbody, valid SSL requirement, five second200deadline, one hour then six hour retry - MyDHL API reference, read on 2026-09-21: test and production base URLs, pre-emptive Basic auth, the eight service categories, the 500 a day test quota, the legal restriction on sharing rating data
- Official DHL Unified Shipment Tracking OpenAPI specification, version 1.5.2, including the
Service,StatusCodeandReferenceTypeenums, the@policymasking markers,ProofOfDelivery,pieceIdsand the RFC 7807 problem detail error shape. Read on 2026-09-21 from a public mirror at alixdecr/PRAB, cross checked against the 1.5.8 copy in jentic/jentic-public-apis - karrio MyDHL connector, an open source client whose
schemasfolder holds the MyDHL rate, shipment, tracking and pickup request and response bodies quoted above, and whoseproxy.pysupplied the MyDHL endpoint paths - stores-com/dhl-ecommerce-solutions, an open source DHL eCommerce Solutions Americas client which supplied the
api.dhlecs.comandapi-sandbox.dhlecs.comhosts, the/auth/v4/accesstokenexchange and the/shipping/v4/*and/tracking/v4/*paths