How to Build Real-Time Delivery Estimates with Tracking Data
One of the most common questions customers ask is “When will my package arrive?” While carriers provide estimated delivery dates, they’re often vague or inaccurate. By analyzing real-time tracking events, you can build smarter, more accurate delivery estimates that keep customers informed and reduce support inquiries.
Why Standard ETAs Fall Short
Most carrier-provided ETAs are set at the time of shipment and rarely update. They don’t account for:
- Customs delays for international shipments
- Weather-related disruptions
- Carrier sorting facility backlogs
- Last-mile delivery challenges
By processing real-time tracking events, you can dynamically adjust estimates as packages move through the delivery network.
Setting Up Tracking Event Collection
First, subscribe to tracking updates using WhereParcel webhooks:
// Register a webhook for tracking updates
const response = await fetch('https://api.whereparcel.com/v2/webhooks', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://your-app.com/webhooks/tracking',
events: ['tracking.updated', 'tracking.delivered'],
}),
});
Processing Events for ETA Calculation
Each tracking event contains status information and timestamps that you can use to build estimates:
function calculateEstimatedDelivery(events, carrier, destination) {
const latestEvent = events[0];
// Base estimates by status
const estimateMap = {
'picked_up': { minDays: 2, maxDays: 5 },
'in_transit': { minDays: 1, maxDays: 3 },
'out_for_delivery': { minDays: 0, maxDays: 0 },
'customs_clearance': { minDays: 1, maxDays: 7 },
'arrived_at_destination': { minDays: 1, maxDays: 2 },
};
const estimate = estimateMap[latestEvent.status] || { minDays: 2, maxDays: 7 };
const now = new Date();
return {
earliest: addBusinessDays(now, estimate.minDays),
latest: addBusinessDays(now, estimate.maxDays),
confidence: calculateConfidence(events),
};
}
Building an Adaptive Estimation Model
For more accurate estimates, track historical delivery times by carrier and route:
// Store delivery times for completed shipments
async function recordDeliveryTime(shipment) {
const pickupDate = shipment.events.find(e => e.status === 'picked_up')?.timestamp;
const deliveryDate = shipment.events.find(e => e.status === 'delivered')?.timestamp;
if (pickupDate && deliveryDate) {
await db.deliveryTimes.insert({
carrier: shipment.carrier,
originCountry: shipment.origin.country,
destinationCountry: shipment.destination.country,
transitDays: daysBetween(pickupDate, deliveryDate),
recordedAt: new Date(),
});
}
}
// Calculate average delivery time for a route
async function getHistoricalAverage(carrier, origin, destination) {
const records = await db.deliveryTimes
.find({ carrier, originCountry: origin, destinationCountry: destination })
.sort({ recordedAt: -1 })
.limit(100);
if (records.length < 10) return null; // Not enough data
const times = records.map(r => r.transitDays);
return {
average: Math.round(times.reduce((a, b) => a + b) / times.length),
p90: percentile(times, 90),
sampleSize: records.length,
};
}
Displaying Estimates in Your UI
Present delivery estimates clearly with appropriate confidence levels:
function DeliveryEstimate({ estimate }) {
const { earliest, latest, confidence } = estimate;
if (confidence === 'high') {
return (
<div className="estimate high-confidence">
<span>Expected delivery: {formatDate(earliest)}</span>
</div>
);
}
return (
<div className="estimate">
<span>
Estimated delivery: {formatDate(earliest)} – {formatDate(latest)}
</span>
{confidence === 'low' && (
<small>Estimate may change based on customs processing</small>
)}
</div>
);
}
Handling Edge Cases
International Shipments with Customs
Customs processing is the biggest variable in delivery estimates. When a package enters customs, widen your estimate range:
if (latestEvent.status === 'customs_clearance') {
// Customs can take 1-14 days depending on country
estimate.latest = addBusinessDays(now, getCustomsEstimate(destination.country));
estimate.confidence = 'low';
}
Delivery Exceptions
When tracking shows a failed delivery attempt or exception, update the estimate accordingly:
if (latestEvent.status === 'delivery_exception') {
estimate.earliest = addBusinessDays(now, 1); // Next business day retry
estimate.latest = addBusinessDays(now, 3);
estimate.note = 'Delivery was attempted. The carrier will retry.';
}
Key Metrics to Track
Monitor these metrics to improve your estimation accuracy over time:
| Metric | Target | Description |
|---|---|---|
| On-time accuracy | > 85% | Deliveries within estimated window |
| Average error | < 1 day | Mean absolute error in days |
| Customer inquiries | Decreasing | WISMO calls related to delivery timing |
| Confidence calibration | Aligned | High-confidence estimates should be right 95%+ of the time |
Putting It All Together
Combining real-time tracking events with historical data gives you a powerful estimation system:
- Collect — Subscribe to tracking webhooks for all shipments
- Analyze — Process events and calculate estimates using status + historical data
- Display — Show estimates with appropriate confidence levels
- Learn — Record actual delivery times to improve future estimates
- Notify — Alert customers when estimates change significantly
By building this system on top of WhereParcel’s multi-carrier tracking API, you get consistent tracking events across 500+ carriers, making it practical to build accurate estimates at global scale.
For more on setting up webhooks, see our Webhook Best Practices guide.