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:

MetricTargetDescription
On-time accuracy> 85%Deliveries within estimated window
Average error< 1 dayMean absolute error in days
Customer inquiriesDecreasingWISMO calls related to delivery timing
Confidence calibrationAlignedHigh-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:

  1. Collect — Subscribe to tracking webhooks for all shipments
  2. Analyze — Process events and calculate estimates using status + historical data
  3. Display — Show estimates with appropriate confidence levels
  4. Learn — Record actual delivery times to improve future estimates
  5. 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.