How to Split a Multi-Warehouse Order Across ShipStation Warehouses
When a new Shopify order arrives, this Spojit workflow lists your ShipStation warehouses, groups the order's line items by their stocking location, then calls a label-creation subworkflow once per warehouse so each parcel ships from the right site.
What This Integration Does
Brands that hold stock in more than one warehouse face a routine problem: a single customer order can contain items that live in different locations. Shipping the whole order from one site means costly transfers or split-second backorders. This workflow solves that by reading each order line, deciding which warehouse stocks that SKU, and then producing a separate shipment per warehouse so every parcel leaves from the closest or correct facility. It runs hands-off on every order and posts a short summary to a Slack channel so your fulfillment team can see what shipped from where.
The workflow is started by a Webhook trigger that fires whenever Shopify sends a new-order event. The parsed order body flows into a Connector step that lists your ShipStation warehouses, a Transform step that buckets line items by location, and a Loop that calls a label-creation Subworkflow for each warehouse bucket. Each subworkflow run appears as its own execution-history entry, so a single order produces one parent run plus one child run per warehouse. Re-runs are safe to reason about because the grouping is recomputed from the order body each time; if Shopify retries the webhook, enable dedup on the trigger so the same order is not split twice.
Prerequisites
- A Shopify connection, used to enrich line items and read order detail.
- A ShipStation connection with API access, holding at least two warehouses you can see under
list-warehouses. - A Slack connection with permission to post to your fulfillment channel.
- A Webhook signing connection set to the Shopify scheme so incoming order events are verified by HMAC. See Setting Up a Webhook Trigger.
- A mapping that tells the workflow which warehouse stocks each SKU. The simplest approach is a SKU prefix or tag convention; this tutorial uses the warehouse name embedded in each item's SKU prefix.
- A separate child workflow that creates one ShipStation label (built in Step 5 below) and accepts an
Inputobject.
Step 1: Trigger on a new Shopify order with a Webhook
Add a Trigger node and set its type to Webhook. Choose your Shopify signing connection so the request is HMAC-verified, then copy the generated workflow URL into Shopify's order-creation notification settings. The webhook output is the parsed JSON order body, available downstream as {{ trigger.body }}. Turn on event-id deduplication using Shopify's event header so a replayed delivery does not split the same order twice. The trigger returns 202 with an executionId immediately, which keeps Shopify's delivery fast while the rest of the workflow runs asynchronously.
Step 2: List your ShipStation warehouses
Add a Connector node in Direct mode, choose the ShipStation connector, and select the list-warehouses tool. It takes no inputs and returns every fulfillment location on your account. Save the result to a variable such as warehouses so later steps can match a location name to its warehouseId and its originAddress. Direct mode is the right choice here because this is a single, predictable call with no AI cost.
Step 3: Group line items by stocking warehouse
Add a Transform node to reshape the order into one bucket per warehouse. Read each line item from {{ trigger.body.line_items }}, decide which warehouse stocks it, and emit an object keyed by warehouseId whose value is the list of items for that site. If your SKU convention encodes the site (for example SYD-1001 for the Sydney warehouse), match the prefix to the matching entry in {{ warehouses.data }}. Produce a compact structure the loop can iterate over, for example:
{
"groups": [
{
"warehouseId": 12345,
"warehouseName": "Sydney DC",
"originPostalCode": "2000",
"items": [
{ "sku": "SYD-1001", "name": "Wide-brim hat", "quantity": 2 }
]
},
{
"warehouseId": 67890,
"warehouseName": "Melbourne DC",
"originPostalCode": "3000",
"items": [
{ "sku": "MEL-2044", "name": "Canvas tote", "quantity": 1 }
]
}
]
}
Store the result in a variable such as grouped. If a SKU does not match any warehouse, route it to a default group rather than dropping it, so nothing silently fails to ship.
Step 4: Loop over each warehouse group
Add a Loop node set to ForEach and point it at {{ grouped.groups }}. Each pass exposes the current bucket as the loop item, for example {{ group.warehouseId }} and {{ group.items }}. Inside the loop body you will call the label-creation subworkflow once per warehouse. Because each group is independent, a single multi-warehouse order produces several child runs, one per site, each carrying only the items stocked there.
Step 5: Build the label-creation subworkflow
Create a second workflow that produces exactly one ShipStation label, so it can be reused by any parent. Give it a Manual trigger so its Input becomes the run body. Inside, add a Connector node in Direct mode using the ShipStation connector and the create-shipment-label tool. Map the shipment object from the input, setting shipFrom to the warehouse origin, shipTo to the order's shipping address, and the items, weight, carrierCode, and serviceCode for that parcel:
{
"shipment": {
"carrierCode": "{{ input.carrierCode }}",
"serviceCode": "{{ input.serviceCode }}",
"packageCode": "package",
"weight": { "value": "{{ input.weight }}", "units": "grams" },
"shipFrom": {
"name": "{{ input.warehouseName }}",
"postalCode": "{{ input.originPostalCode }}"
},
"shipTo": "{{ input.shipTo }}"
}
}
Add a Response node (or simply let the connector result be the final output) so the child returns the tracking number and label detail to the parent. Optionally call get-rates first to pick the cheapest carrier before buying the label. See Using Subworkflow Nodes for how child output flows back to the parent.
Step 6: Call the subworkflow from the parent loop
Back in the parent loop body, add a Subworkflow node. Set Workflow to the label-creation workflow from Step 5 and pass the per-warehouse Input:
{
"warehouseName": "{{ group.warehouseName }}",
"originPostalCode": "{{ group.originPostalCode }}",
"shipTo": {{ trigger.body.shipping_address }},
"carrierCode": "{{ group.carrierCode }}",
"serviceCode": "{{ group.serviceCode }}",
"weight": "{{ group.totalWeight }}"
}
The parent pauses while each child runs, then continues with the child's returned tracking detail. Collect each child's output across loop passes so the next step can summarize all parcels.
Step 7: Post a fulfillment summary to Slack
After the loop, add a Connector node in Direct mode using the Slack connector and the send-message tool. Set the channel to your fulfillment channel and build a message that lists each warehouse, its parcel count, and its tracking number, for example Order {{ trigger.body.order_number }} split across {{ grouped.groups.length }} warehouses followed by one line per child result. This gives your team a single audit line per order showing what shipped from where.
Tips
- Keep the label-creation logic in the subworkflow only. Changes to it take effect immediately for every parent, so you can swap carriers or adjust packaging in one place.
- If many orders span the same warehouses, consider caching
list-warehousesoutput in a short-lived variable rather than calling it on every run; warehouse lists rarely change intraday. - Use a Parallel node instead of a serial Loop only if your warehouse count is small and you want all labels bought concurrently; for variable group counts the ForEach loop is simpler.
- Ask Miraxa, the intelligent layer across your automation, to scaffold the grouping logic with a prompt like "Add a Transform node that groups
{{ trigger.body.line_items }}by warehouse using the SKU prefix," then fine-tune in the properties panel.
Common Pitfalls
- Unmatched SKUs. If an item's SKU does not match any warehouse name, it can vanish from every group. Always route unmatched items to a default bucket and flag them in the Slack summary.
- Webhook replays. Shopify can deliver the same order event more than once. Without dedup on the Webhook trigger you will create duplicate labels. Enable event-id dedup using Shopify's event header.
- Origin address mismatch. The
shipFromincreate-shipment-labelmust reflect the actual warehouse, not your default. Map it from the matchedlist-warehousesentry, not a hardcoded value. - Weight and rate gaps. ShipStation rejects label creation when weight is missing or zero. Compute a real per-parcel weight in the Transform step, and call
get-ratesif you need a validserviceCodebefore buying.
Testing
Before enabling the workflow, place a small test order in Shopify that deliberately spans two warehouses (one item per site). Use the Run button or send a sample order body to the webhook URL, then open the execution history: you should see one parent run plus one child run per warehouse, and the Slack summary should list each warehouse with a tracking number. Confirm in ShipStation that each label's origin matches the expected site. Once the split is correct on a two-item order, scale up to real traffic.