# 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 > (). 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**: . 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](README.md#shared-groundwork-do-this-once-before-any-carriers-api-step) first. Set `shipping_methods.carrier = "dhl"` for your DHL options. ### Credentials ```bash 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`: ```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. **Token** → `POST {base}/.../token` with `grant_type=client_credentials` + key/secret → `access_token` (Bearer; cache until it expires). 2. **Create shipment** → `POST` 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`) ```rust use loco_rs::prelude::*; use crate::shared::settings; async fn bearer(ctx: &AppContext) -> Result { 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 { 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 . ## 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