How to Sync Shippit Tracking Status Back to Shopify Orders

Build a Spojit workflow that runs on a schedule, loops your open Shopify orders, asks Shippit for the latest carrier status on each one, and writes that status back onto the Shopify order so your team and customers always see where a parcel is.

What This Integration Does

Once an order ships, the carrier keeps moving the parcel through statuses like "in transit", "out for delivery", and "delivered", but Shopify does not learn about those changes on its own. Support staff end up logging into the Shippit portal to chase a tracking number every time a customer asks "where is my order". This workflow closes that gap: on a fixed schedule it reads each open Shopify order, calls Shippit for the parcel's current status, and stamps that status onto the Shopify order as a tag and note. Your order list becomes a live tracking board, and the value compounds across hundreds of shipments because nobody has to look anything up by hand.

The run model is a polling sync, not an event push. A Schedule trigger fires the workflow (for example every 30 minutes during business hours). Each run pulls the current page of open orders from Shopify, iterates them with a Loop, fetches fresh tracking from Shippit, and updates only the orders whose carrier status has actually moved since last time. Because the workflow compares the new status against the status it last wrote, re-runs are idempotent: an order that has not changed is read but left untouched, so repeated runs do not churn tags or fire duplicate notes. State lives entirely on the Shopify order (its tags and note), so the workflow holds no memory of its own between runs.

Prerequisites

  • A Shopify connection with permission to read and update orders. See the Shopify connector article to set it up.
  • A Shippit connection authorized for your merchant account. See the Shippit connector article.
  • Orders in Shopify that already carry a Shippit tracking number on their fulfillment (Shippit numbers usually start with PP). If you create those shipments from Spojit, see how Spojit books Shippit orders for fulfillment for the same pattern on Shopify.
  • A naming convention for the status tags you will write (for example shippit:in_transit, shippit:delivered) so they are easy to filter in the Shopify admin.

Step 1: Start the workflow on a Schedule trigger

Add a Trigger node and set its type to Schedule. Schedules use a 5-field Unix cron expression plus an IANA timezone. To poll every 30 minutes between 8am and 6pm on weekdays in Sydney, use:

Cron:     */30 8-18 * * 1-5
Timezone: Australia/Sydney

A single trigger can hold more than one schedule, so you can add a lighter overnight cadence later. The trigger output is {{ scheduledAt }}, which is the only data the run starts with: everything else is fetched from Shopify and Shippit downstream.

Step 2: List open orders from Shopify

Add a Connector node in Direct mode, choose the Shopify connection, and pick the list-orders tool. Use Shopify query syntax in the query field to fetch only orders that are shipped but not yet delivered/closed, and set first to a page size you can comfortably process in one run:

query: fulfillment_status:shipped status:open
first: 50

list-orders returns paginated results and exposes a cursor. If you expect more than one page, capture the cursor and feed it to the after field on a follow-up call, or narrow the query (for example by adding created_at:>2024-01-01) so a single page covers your active shipments. The list of orders is available to the next step as {{ list_orders.result.orders }}.

Step 3: Loop over each order

Add a Loop node in ForEach mode and point it at the order list from Step 2, for example {{ list_orders.result.orders }}. Inside the loop body each iteration exposes the current order as {{ order }}. Everything from Step 4 onward lives inside this loop body so it runs once per order. Keep the page size from Step 2 reasonable so each run stays well inside a sensible duration and inside both Shopify and Shippit rate limits.

Step 4: Read the order's tracking number, then ask Shippit for status

Inside the loop, add a Connector node in Direct mode on the Shopify connection using get-order with id set to {{ order.id }}. This returns the order with its line items, fulfillments, and transactions; the Shippit tracking number lives on the fulfillment. Pull it into a clean variable with a Transform node so the rest of the loop is tidy, for example mapping the first fulfillment's tracking number to trackingNumber.

Now add another Connector node in Direct mode on the Shippit connection and pick the track-order tool. Set tracking_number to the value you extracted:

tracking_number: {{ get_order.result.order.fulfillments.0.trackingNumber }}

track-order returns the parcel's current tracking status and its event history. Read the latest carrier state from {{ track_order.data }}. If an order has no Shippit tracking number yet, guard this step with a Condition node (Step 5 pattern) so you skip the Shippit call rather than send it a blank tracking number.

Step 5: Only update when the carrier status has changed

Add a Condition node that compares the fresh Shippit status against the status you last wrote to the Shopify order. Because you record the status as a tag like shippit:in_transit, you can check whether the order's current tags already contain the new status. Configure the condition so the true branch runs only when the latest Shippit status differs from what is already on the order:

{{ track_order.data.state }}  is not contained in  {{ order.tags }}

The false branch does nothing and the loop moves to the next order. This comparison is what makes the workflow idempotent: an unchanged parcel is read but never re-tagged, so scheduled re-runs stay quiet until something actually moves.

Step 6: Write the new status back to the Shopify order

On the true branch, add a Connector node in Direct mode on the Shopify connection and pick update-order. The update-order tool sets an order's tags and note. Build a new tag set that keeps the order's existing non-status tags and adds the fresh status, and write a human-readable note. Because tags replaces the full tag set, compose the new array with a Transform node first (strip any old shippit:* tag, then append the new one):

id:   {{ order.id }}
tags: {{ newTags }}
note: Shippit status: {{ track_order.data.state }} (updated {{ scheduledAt }}). Tracking {{ trackingNumber }}.

After this node the Shopify order carries the current carrier status, so anyone viewing it in the Shopify admin or in a connected support tool sees where the parcel is without leaving Shopify.

Step 7 (optional): Notify on key milestones

If you want a proactive nudge when a parcel hits a milestone such as "delivered" or "out for delivery", add a second Condition node after Step 6 that checks {{ track_order.data.state }} for those values, then a Send Email node on its true branch. Set Recipients to the customer email from the order (for example {{ order.email }}), a templated Subject, and a body that references the tracking number. Remember external recipients must be on your org allowlist under Settings -> General -> Email recipients, and Send Email counts toward your monthly email allowance. For a Slack heads-up to your fulfillment channel instead, see how to send tracking notifications via Slack.

Tips

  • Keep the Step 2 page size and Step 1 cadence balanced. A tight schedule over a large first value multiplies Shopify and Shippit calls; polling every 30 minutes over 50 active orders is usually plenty for end-of-day visibility.
  • Use a consistent tag prefix such as shippit: for every status. It makes the Step 5 comparison reliable and lets you filter the Shopify admin to "everything currently in transit" in one click.
  • If you process more orders than fit on one page, drive pagination from the after cursor that list-orders returns, or split the schedule by store segment so each run handles a slice.
  • Let Miraxa, the intelligent layer across your automation, scaffold the skeleton for you. Try a prompt like "Build a workflow on a 30 minute schedule that lists shipped Shopify orders, loops them, calls Shippit track-order on each, and updates the order tags when the status changes", then fine-tune each node in the properties panel.

Common Pitfalls

  • Expecting update-order to set fulfillment status or tracking directly. In Spojit, Shopify's update-order only changes an order's tags and note. This workflow records the carrier status as a tag and note rather than rewriting Shopify's native fulfillment object.
  • Replacing all tags by accident. The tags field replaces the full tag set, so always rebuild the array from the order's existing tags plus the new status. Skipping the merge wipes other tags your store relies on.
  • Sending a blank tracking number to Shippit. Orders that were marked shipped manually may have no Shippit number on the fulfillment. Guard the Shippit call with a condition so track-order never receives an empty tracking_number.
  • Timezone drift on the schedule. The cron expression is evaluated in the IANA timezone you choose. Set it to your fulfillment team's timezone (for example Australia/Sydney) so "business hours" lines up with reality, and remember daylight-saving shifts are handled by the named zone, not by editing the cron.

Testing

Before turning on the schedule, validate on a tiny scope. Temporarily set the Step 2 query to a single known order (for example by adding its name to the query) or set first to 1, then use the Run button on the trigger to execute once on demand. Open the execution log and confirm the order was listed, get-order returned the expected fulfillment, track-order came back with a status, the condition evaluated as you expected, and update-order wrote the right tag and note. Run it a second time with no carrier change to prove the condition keeps it idempotent (no second update). Once the small scope behaves, widen the query and enable the schedule.

Learn More

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