How to Auto-Fulfill Shopify Orders with DHL Express
Automatically create DHL Express shipments when new Shopify orders come in.
What This Integration Does
Shopify's native fulfillment options handle a few popular carriers well, but DHL Express isn't always among them, and even when it is the integration is shallow - you can't easily customise rate shopping, customs blocks, or pickup batching. Building this workflow in Spojit puts the entire fulfillment loop in your control: every paid Shopify order produces a DHL waybill, the tracking number lands back on the order, and Shopify's standard "shipped" notification fires automatically.
The workflow runs on a Shopify webhook for paid orders. It fetches the full order, transforms it into DHL's shape, optionally rate-shops between DHL services, creates the shipment, and writes the tracking number back to Shopify as a fulfillment so the customer sees a normal shipping update in their account.
Prerequisites
- A Shopify connection with
read_orders,write_orders, andwrite_fulfillmentsscopes. - A DHL Express connection (account number plus XML Services or MyDHL API credentials).
- HS commodity codes on Shopify product metafields for any product shipped internationally.
- A configured pickup address on the DHL account.
Step 1: Shopify Webhook Trigger
Drop a Trigger node and set its type to Webhook. In Shopify Admin, register the trigger URL against the orders/paid topic. This avoids the timing edge cases that come with orders/create (where payment can still be pending).
Step 2: Fetch the Full Order
The Shopify webhook payload is usually sufficient, but if you've configured the webhook to send a slim body, add a Connector node on shopify with get-order and pass {{ trigger.body.id }} to get the canonical order JSON including line items and shipping address.
Step 3: Transform - Map Shopify to DHL
Add a Transform node converting the Shopify order into DHL's expected payload:
{
"recipient": {
"name": "{{ order.shipping_address.name }}",
"address1": "{{ order.shipping_address.address1 }}",
"city": "{{ order.shipping_address.city }}",
"postcode": "{{ order.shipping_address.zip }}",
"country_code": "{{ order.shipping_address.country_code }}"
},
"packages": [{
"weight_kg": "{{ (order.total_weight / 1000) }}",
"length_cm": 30, "width_cm": 20, "height_cm": 10
}],
"customs": {
"invoice_value": "{{ order.total_price }}",
"currency": "{{ order.currency }}"
}
}
Use a Condition node to strip the customs block on domestic shipments.
Step 4: Rate-Shop with DHL
Add a Connector node on dhl-express using get-rates. The response lists service codes (e.g. P, U) with cost and ETA. Use an array sort step or an inline expression to pick the cheapest service that meets your delivery promise.
Step 5: Create the DHL Shipment
Add a Connector node on dhl-express and pick create-shipment. Pass the transformed payload plus the chosen service code. The response includes a waybill (tracking) number and label PDF.
Step 6: Write Back to Shopify and Notify
Add a Connector node on shopify with update-order (or a fulfillment-create call via raw-graphql) to attach the DHL tracking number, carrier name "DHL Express", and tracking URL. This is what triggers Shopify's customer shipping confirmation email. Fan out with a Parallel node to a slack send-message step that posts the order summary and label link into #fulfillment for the warehouse team.
Tips
- Use a Condition node to route domestic orders to a cheaper local carrier (Australia Post, USPS) and only reserve DHL for international.
- Add a slack
send-messageerror branch on every Connector call so failures aren't silent. - Persist the label PDF to a long-term bucket - DHL only retains them for a limited window.
- Run a daily companion workflow that calls
track-shipmenton open fulfillments and pushes in-transit status back into Shopify.
Common Pitfalls
- Total weight in grams:
order.total_weightis grams, not kilos. Divide before sending to DHL or you'll get bizarre rates. - Missing HS codes: international orders without per-line HS codes will be rejected. Validate before
create-shipmentand route problem orders to a review queue. - Shopify webhook retries: a slow workflow run can cause Shopify to retry and produce duplicate DHL shipments. Idempotency-key on the order ID or check Shopify fulfillments before creating a new DHL shipment.
- Address line truncation: DHL silently truncates address fields. Reject orders with overflowing addresses up front rather than creating bad waybills.
Testing
Place a test order on your Shopify store using a low-value, low-weight test product to a known domestic address. Run the workflow once manually, confirm the waybill is created and the Shopify order shows the tracking number on the fulfillment. Then place a test international order to confirm customs handling works. Only after both pass should you enable the production webhook.