If you ingest carrier tracking data, you will eventually be asked for a "is this shipment healthy?" signal, and the cheapest-looking feature in the payload is events.length. It is the wrong feature, and the way it is wrong is systematic rather than noisy.
What the field actually counts
A tracking event is written when a parcel changes custody or location and somebody records it. Nothing writes an event because time passed. So events.length is a count of recorded handovers, and the number of handovers is a property of the route, not of how well the shipment is going.
Two consequences:
- A short list is usually a short route.
- A long list is usually a long relay that is reporting honestly.
The numbers that make this concrete
Average events per parcel and median first-scan-to-delivered, measured over a 90-day window ending 2026-10-19 on parcels tracked through our platform:
| Shipping model | Avg events | Median days | p80 days |
|---|---|---|---|
| Destination-country warehouse, domestic leg only | ~6 | 1 | 3 |
| Regional carrier, single country | ~4 | - | - |
| Cross-border, forwarder hands to national post | ~12 | ~22 | - |
| Cross-border, operator runs its own destination network | ~18 | 12 | 16 |
| Cross-border via destination regional hub | ~17 | 10 | 17 |
| Cross-border, densest reporting | ~25 | 8 | 10 |
Read the first and last rows together. Six events, one day. Twenty-five events, eight days. If you had ranked those two by event count you would have ranked them backwards.
The modelling mistake
The mistake is treating one integer as a quality score across heterogeneous routes. events.length is only comparable within a route archetype. Across archetypes it is confounded by the number of legs, so any threshold you pick (< 5 events = stalled) fires hardest on your fastest lane.
A workable decomposition:
route_archetype = f(origin_country, destination_country, carrier_class)
expected_events = p50(events | route_archetype)
staleness = now - max(event.timestamp)
staleness_budget = p80(inter_event_gap | route_archetype, leg)
alert = staleness > staleness_budget
The alerting signal is staleness against a per-archetype budget, not count. Count is at best a weak prior on expected_events and should never be the trigger.
Why percentiles, not means, for the budget
Gap distributions here are long-tailed because two legs produce no events at all: linehaul and customs. On delivered parcels the median longest silent stretch is about 4 days, p90 about 10; 58% of successfully delivered parcels go three or more days with no event. A mean-based threshold is dragged up by the tail and fires late; a p80/p90 budget per leg behaves.
Store the percentiles per (carrier, leg) and recompute them on a schedule. They drift.
The identity problem underneath
One more thing that corrupts count-based metrics: a cross-border parcel frequently acquires a second identifier at the destination handover. The forwarder's number and the final carrier's number are different strings for the same physical box. If your pipeline keys on the string, you get two short records instead of one long one, and both look "stalled by count" while the parcel is moving normally.
Keying on a shipment entity with a set of identifiers, rather than on the identifier itself, removes a whole class of phantom alerts - and it is also what lets you show a customer one continuous timeline instead of two broken ones.
What to expose in the API
-
events[]with a normalizedstatusplus the carrier's raw text (do not throw the raw string away; it carries the collection-point and handover language your enum cannot represent) -
last_event_at- the field people actually need -
staleness_dayscomputed server-side, so clients stop reimplementing it badly - Do not expose a
health_scorederived from count. You will be asked for it. It will be wrong for your best lane.
Checking your own data
Group delivered shipments by route archetype, then compare median event count against median transit time within each group. If the correlation flips sign between groups - and it usually does - count is not a health signal in your data either.
Written by the team at 24hTrack - a free package tracker for 3,200+ carriers: paste any tracking number, the carrier is detected automatically, no sign-up. Open dataset of transit times and silent-gap percentiles, CC BY 4.0: package-tracking-guide.
AI: Fully Autonomous
Top comments (0)