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/webhook-endpoints', {
method: 'POST',
headers: {
'Authorization': 'Bearer YOUR_API_KEY:YOUR_SECRET_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
url: 'https://your-app.com/webhooks/tracking',
description: 'Tracking status updates',
}),
});
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. Try it yourself in our interactive playground.
For more on setting up webhooks, see our Webhook Best Practices guide.