How to Compare DHL Express and ShipStation Rates Before Buying a Label

When a new paid Shopify order arrives, fetch live shipping rates from both DHL Express and ShipStation, let an Agent-mode Connector node pick the cheapest compliant service, and post the decision to Slack for your fulfillment team.

What This Integration Does

Shipping cost is one of the few line items you can shave on every single order, but only if someone compares carriers before a label is bought. Doing that by hand for each order does not scale. This Spojit workflow does the comparison automatically: the moment a Shopify order is paid, it asks DHL Express and ShipStation for live rates to the buyer's address, then hands both quotes to Miraxa, the intelligent layer across your automation, to choose the cheapest service that still meets your delivery and customs rules. The chosen carrier, service level, and price land in a Slack channel where your team can act on them.

The run model is event-driven. A Webhook trigger fires once per paid Shopify order, verified by HMAC against a Shopify signing connection. Two Connector nodes in Direct mode call the get-rates tool on each carrier in a Parallel node so both quotes return at the same time. An Agent-mode Connector node compares them and returns a structured decision, and a final Connector node posts that decision to Slack. The workflow only reads rates and reports a recommendation: it does not buy a label or mutate the order, so re-running the same order simply produces a fresh comparison with no side effects beyond another Slack message.

Prerequisites

  • A Shopify connection, plus a Shopify-scheme signing connection used to verify the webhook. See Setting Up a Webhook Trigger and Setting Up a Webhook Connection.
  • A DHL Express connection with rating access, and your DHL origin postal code and country.
  • A ShipStation connection, and at least one carrier code enabled in your ShipStation account (run list-carriers once to confirm the code, for example stamps_com or fedex).
  • A Slack connection with permission to post to your fulfillment channel.
  • The Shopify orders/paid webhook configured to POST to this workflow's URL (set up in Step 1).
  • Your warehouse origin postal code and country, and a typical package weight to send when an order does not carry line-item weights.

Step 1: Receive the paid order with a Webhook trigger

Add a Trigger node and set its type to Webhook. Choose the Shopify scheme and select your Shopify signing connection so Spojit validates the HMAC signature on every request. Copy the generated workflow URL into your Shopify admin as the destination for the orders/paid topic. When Shopify posts an order, the trigger returns the parsed JSON body and a 202 response with an executionId. Downstream nodes read the order through {{ trigger }}, for example {{ trigger.id }}, {{ trigger.total_weight }}, and the buyer destination in {{ trigger.shipping_address }}.

Step 2: Normalize the destination and weight with a Transform node

Add a Transform node so both rate calls read from one clean object. Map the buyer's postal code, country, state, and city from {{ trigger.shipping_address }}, and compute a package weight. DHL Express expects weight in kilograms, while ShipStation expects a weight object whose units you control, so produce both. A simple structured output looks like this:

{
  "toPostalCode": "{{ trigger.shipping_address.zip }}",
  "toCountry": "{{ trigger.shipping_address.country_code }}",
  "toState": "{{ trigger.shipping_address.province_code }}",
  "toCity": "{{ trigger.shipping_address.city }}",
  "weightKg": 2.5,
  "isInternational": false
}

Default the weight to a sensible value when the order has none. See Using Transform Nodes (Structured Mode) for mapping details.

Step 3: Fetch both carriers' rates in parallel

Add a Parallel node with two branches so both quotes return at once instead of one after the other.

In the first branch, add a Connector node in Direct mode for DHL Express and pick the get-rates tool. Map its fields from your Transform output and your warehouse origin:

{
  "originPostalCode": "2000",
  "originCountryCode": "AU",
  "destinationPostalCode": "{{ transform.toPostalCode }}",
  "destinationCountryCode": "{{ transform.toCountry }}",
  "destinationCityName": "{{ transform.toCity }}",
  "weight": "{{ transform.weightKg }}",
  "plannedShippingDate": "{{ date.now }}",
  "isCustomsDeclarable": "{{ transform.isInternational }}",
  "unitOfMeasurement": "metric"
}

In the second branch, add a Connector node in Direct mode for ShipStation and pick the get-rates tool. ShipStation requires a carrierCode and a weight object:

{
  "carrierCode": "stamps_com",
  "fromPostalCode": "2000",
  "toCountry": "{{ transform.toCountry }}",
  "toPostalCode": "{{ transform.toPostalCode }}",
  "toState": "{{ transform.toState }}",
  "weight": { "value": 2.5, "units": "grams" }
}

Name the result variables clearly, for example dhlRates and shipstationRates, so the next step can reference both.

Step 4: Let an Agent-mode Connector node pick the cheapest compliant service

Add a Connector node and switch it to Agent mode so Miraxa, the intelligent layer across your automation, can read both quote sets and reason about them rather than you hand-coding the comparison. Write a prompt that passes both rate objects and states your rules, then attach a Response Schema so the output is reliable JSON the Slack step can read. A prompt such as:

Compare these two sets of shipping quotes and choose the single
cheapest service that delivers in 5 business days or fewer.

DHL Express rates: {{ dhlRates }}
ShipStation rates: {{ shipstationRates }}

Ignore any service that does not return a price. Prefer the lower
total cost. Return the carrier, service name, price, currency, and
a one-line reason.

Set the Response Schema to force the shape you expect:

{
  "type": "object",
  "properties": {
    "carrier":  { "type": "string" },
    "service":  { "type": "string" },
    "price":    { "type": "number" },
    "currency": { "type": "string" },
    "reason":   { "type": "string" }
  },
  "required": ["carrier", "service", "price", "currency", "reason"]
}

Store the result as choice. For the trade-offs between the two modes, see How to Choose Between Agent Mode and Direct Mode.

Step 5: Post the decision to Slack

Add a Connector node in Direct mode for Slack and pick the send-message tool. Target your fulfillment channel and build the message from the order and the choice output:

{
  "channel": "#fulfillment",
  "text": "Order {{ trigger.name }}: ship via {{ choice.carrier }} ({{ choice.service }}) for {{ choice.price }} {{ choice.currency }}. {{ choice.reason }}"
}

Your team now sees the recommended carrier and price the moment the order is paid, and can buy the label in DHL Express or ShipStation with confidence.

Step 6: Connect the nodes and save

Wire the Webhook trigger to the Transform node, the Transform node to the Parallel node, the Parallel node to the Agent-mode Connector node, and that node to the Slack Connector node. Save and enable the workflow. If you prefer to scaffold it quickly, ask Miraxa, for example: "Build a workflow that watches a Shopify webhook, fetches DHL Express and ShipStation rates in parallel, picks the cheapest with an Agent-mode node, and posts it to Slack," then fine-tune each node in the properties panel.

Tips

  • Run ShipStation's list-carriers tool once in a throwaway Direct-mode node to confirm the exact carrierCode values your account supports before hard-coding one.
  • Keep the comparison itself in Agent mode but keep both get-rates calls in Direct mode: the rate fetches are deterministic and cost no AI credits, so only the judgment step spends them.
  • Set plannedShippingDate to today or the next business day using the Date & Time connector's now tool so DHL Express returns rates that are actually bookable.
  • For international orders, set isCustomsDeclarable to true on the DHL Express call so the returned services reflect customs handling.

Common Pitfalls

  • Unit mismatch: DHL Express get-rates takes a single weight number in kilograms, while ShipStation takes a weight object with its own units. Send the right shape to each or one carrier returns no usable rates.
  • Empty quotes: a carrier may return zero services for an unsupported lane. Tell the Agent prompt to ignore any service without a price, otherwise it may recommend an unbookable option.
  • Webhook replays: Shopify can resend the same orders/paid event. Enable opt-in dedup on the Webhook trigger via an event-id header so a duplicate does not post a second Slack message.
  • Address gaps: orders missing a postal code or country code will fail rating. Default or guard these in the Transform node before the Parallel branches run.

Testing

Before enabling the live webhook, place one low-value test order in Shopify (or replay a captured orders/paid payload) so a single execution runs end to end. Open the run in the execution logs and confirm both get-rates branches returned priced services, the Agent-mode node produced a choice object matching your Response Schema, and the Slack message named a carrier and price. Once one order behaves, point the Shopify webhook at the workflow and enable it for real traffic.

Learn More

Did this answer your question? Thanks for the feedback There was a problem submitting your feedback. Please try again later.