Files
2026-06-17 16:15:22 +02:00

6.2 KiB

DHL integration

DHL is best for home delivery and international/express shipments. Like DPD, nothing DHL-specific is scaffolded here. DHL is mostly an address (home) delivery carrier — pickup points exist (DHL ServicePoint / Packstation, mostly DE) but most shops use DHL for door-to-door, so you can usually skip the pickup widget entirely.

DHL has several separate APIs behind one developer portal (https://developer.dhl.com). Pick the one that matches your service:

  • DHL Parcel DE (Post & Parcel Germany) — Shipping API for German domestic parcels / Packstation.
  • DHL eCommerce (Parcel) APIs for various countries.
  • DHL Express — MyDHL API for international express. Confirm which your contract covers before coding.

1. Get DHL API access

  1. Create an account on the DHL Developer Portal: https://developer.dhl.com.
  2. Create an app and subscribe it to the specific API you need (e.g. "Shipping API" or "MyDHL API"). You receive an API key (client id) + secret.
  3. Separately you need a DHL business/customer account (EKP / account number, billing number) — the developer key alone can't bill shipments. Link your business account credentials to the app.
  4. Most DHL APIs use OAuth2 client-credentials: you exchange key+secret for a short-lived Bearer token, then call the shipping endpoints with it. (Some older endpoints use Basic auth — check your API's docs.)

2. Create the delivery option

At /admin/shipping → "Add delivery option":

  • Name: e.g. DHL or DHL Express (international)
  • Price: your fee
  • Requires pickup point: off for normal home delivery (turn on only if you specifically offer DHL Packstation/ServicePoint and build a picker — see section 4)
  • Active

With the option active, customers can already choose DHL and you can create the label manually in DHL Business Customer Portal. The API (section 3) automates that.


3. Create shipments via the DHL API

Do the shared groundwork first. Set shipping_methods.carrier = "dhl" for your DHL options.

Credentials

DHL_API_KEY=your_client_id
DHL_API_SECRET=your_client_secret
DHL_ACCOUNT_NUMBER=your_ekp_or_billing_number
DHL_API_BASE=https://api-eu.dhl.com        # depends on the specific API

Add matching lines under settings: in config/*.yaml:

  dhl_api_key:        {{ get_env(name="DHL_API_KEY", default="") }}
  dhl_api_secret:     {{ get_env(name="DHL_API_SECRET", default="") }}
  dhl_account_number: {{ get_env(name="DHL_ACCOUNT_NUMBER", default="") }}
  dhl_api_base:       {{ get_env(name="DHL_API_BASE", default="") }}

Flow (OAuth2 + create shipment)

  1. TokenPOST {base}/.../token with grant_type=client_credentials + key/secret → access_token (Bearer; cache until it expires).
  2. Create shipmentPOST the shipment-orders endpoint with the Bearer token: shipper (your account/EKP), consignee (recipient from the order), product code (domestic vs international/express), weight, customs data for non-EU, and references (order_number). COD is a value-added service if you offer it.
  3. Label → the response includes a tracking/shipment number and a label (PDF/base64). Store/print it.

Client sketch (src/integrations/dhl.rs)

use loco_rs::prelude::*;
use crate::shared::settings;

async fn bearer(ctx: &AppContext) -> Result<String> {
    let base   = settings::get(ctx, "dhl_api_base").unwrap_or_default();
    let key    = settings::get(ctx, "dhl_api_key").unwrap_or_default();
    let secret = settings::get(ctx, "dhl_api_secret").unwrap_or_default();
    // POST client_credentials → access_token; cache with expiry.
    todo!()
}

pub async fn create_shipment(ctx: &AppContext, req: super::ShipmentRequest<'_>)
    -> Result<super::ShipmentResult>
{
    let token   = bearer(ctx).await?;
    let account = settings::get(ctx, "dhl_account_number").unwrap_or_default();
    // Build shipment JSON:
    //  - shipper:   your account address (account = EKP/billing number)
    //  - consignee: req.recipient_name / address / city / zip / country
    //  - details:   weight, product code (domestic / express), currency
    //  - refs:      req.order_number
    //  - for international: customs (HS codes, declared value, contents)
    // POST {base}/.../shipments  with Authorization: Bearer {token}
    todo!("parse tracking number + label into ShipmentResult")
}

Wire into the admin "Create shipment" action for carrier == "dhl" orders.

🌍 International note: for shipments outside the EU customs union you must send customs/commodity data (HS codes, declared value, item descriptions). Your order_items only store name + price today — if you ship internationally you'll likely add a customs description/HS-code field to products.


4. (Optional) DHL pickup points

If you offer Packstation / ServicePoint, set "Requires pickup point" on that delivery option and render DHL's Location Finder (a separate DHL API) in the checkout pickup block (the x-show="requiresPoint" section of assets/views/shop/checkout.html), writing the chosen locker id into the existing hidden pickup_point_id / pickup_point_name fields. For Packstation you also need the recipient's DHL post number — an extra field most shops avoid unless targeting Germany.


5. Testing

  • DHL provides a sandbox environment per API (separate base URL + test credentials) on the developer portal. Get a token and create one test shipment there before production.
  • Validate the tracking number on https://www.dhl.com/track.

6. Go-live checklist

  • DHL developer app created + subscribed to the right API
  • DHL business account (EKP/billing number) linked
  • DHL_* env vars set; matching settings: lines added to config/production.yaml
  • Delivery option created in /admin/shipping; carrier = "dhl" set
  • src/integrations/dhl.rs implemented; OAuth token caching working
  • (International) customs data available on products/items
  • Test shipment in DHL sandbox → tracking number stored on order
  • Switched from sandbox to production base URL/credentials