Cross-border tracking is where a lot of ecommerce UIs quietly lie. They show "In transit" the moment a label is printed, but a label is not a package moving. Here is a state model that keeps the truth intact.
The core insight: a tracking number is not evidence of physical carrier acceptance. The events mean different things:
-
LABEL_CREATED: a number exists. Nothing has physically moved. -
ACCEPTED(the first carrier scan): the carrier physically has the parcel. This is the real "shipped" moment. -
IN_TRANSIT,OUT_FOR_DELIVERY,DELIVERED: subsequent milestones.
If you collapse LABEL_CREATED and ACCEPTED into one "shipped" state, customers blame you for "stuck" packages that the carrier never actually received.
const TRANSITIONS = {
LABEL_CREATED: ['ACCEPTED', 'CANCELED'],
ACCEPTED: ['IN_TRANSIT', 'RETURNED', 'EXCEPTION'],
IN_TRANSIT: ['OUT_FOR_DELIVERY', 'EXCEPTION', 'RETURNED'],
OUT_FOR_DELIVERY: ['DELIVERED', 'EXCEPTION'],
DELIVERED: [],
EXCEPTION: ['IN_TRANSIT', 'RETURNED', 'DELIVERED'],
};
function advance(state, event) {
const allowed = TRANSITIONS[state] || [];
return allowed.includes(event) ? event : state; // ignore illegal jumps
}
Two rules that save you support tickets:
Only show "shipped" on ACCEPTED, not LABEL_CREATED. Before that, show "Label created, awaiting carrier pickup." Honest, and it stops the "it has not moved in 3 days" panic when a label sits on a shelf.
Never let an out-of-order webhook regress the state. Carrier feeds arrive late and shuffled. Guard with the transition map above so a stale
IN_TRANSITcannot overwrite a newerDELIVERED.
Model the gap between "we made a label" and "the carrier has the box," and half your tracking complaints disappear. We run this across the carrier mix we handle day to day, and the first-scan-vs-label distinction is the single biggest source of customer confusion when it is hidden.
Top comments (0)