If you are building order tracking into a shop, a CRM or an internal dashboard, there is one modelling decision that quietly breaks everything downstream: storing a single carrier per shipment.
For a domestic parcel that is fine. For a cross-border parcel it is wrong on the first day, and the bug surfaces as support tickets rather than as exceptions in your logs.
What actually happens to a cross-border parcel
A typical China-to-Europe or China-to-US order moves through at least two operators:
- A forwarder collects the parcel, consolidates it, moves it out of the origin country and hands it over. The reference your seller gives you is usually created here.
- A local delivery company in the destination country does the last mile. It is the one that actually knocks.
Those are different companies with different scan vocabularies, different SLAs and often a different reference number. One carrier column has to be wrong about one of them.
Three bugs that fall out of the single-carrier model
1. Your "late" threshold is computed against the wrong clock.
Delivery time is not a property of the shipment, it is a property of the lane. Measured medians differ by a factor of three between a domestic courier and a cross-border forwarder, and the spread inside one carrier is wider still - the 80th percentile can be more than double the median for the same operator. If you alert at median + 2 days globally, you will page yourself constantly on the slow lanes and stay silent on the fast ones.
Model it as expected = f(carrier, leg), and use p80 rather than mean. Means are destroyed by the tail; the tail is the whole point.
2. You treat silence as failure.
There are two structural quiet windows on every cross-border parcel: international line-haul and customs clearance. Nothing is scanned because nothing happens that is scannable - the item is on a plane or in a queue. Days of silence there are normal, and an alert rule that does not know about legs will fire on every single shipment.
3. String-matching the carrier name.
This is the one that surprised me most, so here is a concrete case from our own data.
We run a page for a carrier whose registered English name is Jetspeed (Jetspeed Express International Logistics Co., Ltd., based in China). 99.6% of the searches that reach that page spell it "JetScope" - a company that does not exist. It is simply how the name is heard and typed.
If your integration resolves carriers by matching the string a seller typed into an order field, "JetScope" matches nothing, falls into your unknown bucket, and the shipment silently stops updating. The seller is not wrong in any way they can detect, and your logs show no error.
Practical fixes, in order of how much they buy you:
- Resolve carriers by number shape first, name second. Shapes are far more stable than names: in this case about 99% of the references are
JSD+ 12 digits, which is unambiguous. - Keep an alias table and treat it as data, not code. Misspellings, local trading names and acquired brands all land there.
- Never let name resolution be fatal. An unresolved name should degrade to "try every matcher", not to "stop".
A schema that survives contact with reality
shipment id reference -- what the customer was given status -- normalised, not the carrier's wording delivered_at shipment_leg shipment_id operator -- resolved carrier, nullable reference -- may differ from the shipment reference role -- 'forwarder' | 'last_mile' first_scan_at last_scan_at Two things this buys you immediately: you can answer "who do I chase?" correctly (before the first last-mile scan, nobody local has a record of the parcel, so chasing them is wasted), and you can compute per-leg lateness instead of one global guess.
FAQ
Do I need two tracking numbers?
Often you have one that works end to end, because the forwarder publishes the handover. Sometimes the local carrier issues its own and the seller never passes it on. Model for two; be happy when one is enough.
Why not just use the carrier's own API?
You can, for the carriers that have one. The problem is coverage: the forwarder half of the journey is usually a smaller operator with no public API, and that is exactly the half your customer is asking about.
What should the UI say during a quiet window?
Say what is true: the carrier has published no new scan, the last one was at X, and this stretch is commonly quiet. "No updates" reads as "lost"; the same fact with a reason does not.
Written by the team behind 24hTrack - a free package tracker for 3,200+ carriers. Paste any tracking number, the carrier is detected automatically, no sign-up. There is also a REST API and an MCP server if you want this inside your own stack or your AI assistant.
