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:

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. Try it yourself in our interactive playground.

For more on setting up webhooks, see our Webhook Best Practices guide.