Building a Shipping Analytics Dashboard with Tracking Data

Raw tracking events are a goldmine of shipping intelligence — if you know how to analyze them. By building an analytics dashboard on top of your tracking data, you can identify underperforming carriers, optimize shipping routes, and reduce delivery times. This tutorial walks you through building a practical shipping analytics system.

What to Measure

Before writing code, define the metrics that matter:

Core Delivery Metrics

MetricFormulaWhy It Matters
Average transit timeSum of transit days / Total shipmentsBaseline for delivery speed
On-time delivery rateOn-time deliveries / Total deliveries × 100Carrier reliability indicator
Exception rateShipments with exceptions / Total shipments × 100Quality of shipping experience
First-attempt successDelivered on first try / Total deliveries × 100Last-mile efficiency

Business Metrics

MetricFormulaWhy It Matters
WISMO rateTracking inquiries / Total shipments × 100Customer experience quality
Carrier cost per deliveryTotal carrier costs / Successful deliveriesCost efficiency
Return shipping rateReturn shipments / Total shipments × 100Product or fulfillment issues

Collecting Tracking Data

Use WhereParcel webhooks to stream tracking events into your analytics database:

// Webhook receiver - store events for analytics
app.post('/webhooks/tracking', async (req, res) => {
  const event = req.body;

  // Store raw event for detailed analysis
  await db.trackingEvents.insert({
    trackingNumber: event.data.trackingNumber,
    carrier: event.data.carrier,
    status: event.data.latestEvent.status,
    location: event.data.latestEvent.location,
    timestamp: event.data.latestEvent.timestamp,
    receivedAt: new Date(),
  });

  // Update shipment summary record
  await updateShipmentSummary(event.data);

  res.status(200).json({ received: true });
});

async function updateShipmentSummary(data) {
  const summary = await db.shipmentSummaries.findOne({
    trackingNumber: data.trackingNumber,
  });

  const updates = {
    currentStatus: data.latestEvent.status,
    lastUpdated: new Date(),
    eventCount: (summary?.eventCount || 0) + 1,
  };

  // Record milestone timestamps
  if (data.latestEvent.status === 'picked_up' && !summary?.pickedUpAt) {
    updates.pickedUpAt = data.latestEvent.timestamp;
  }
  if (data.latestEvent.status === 'delivered') {
    updates.deliveredAt = data.latestEvent.timestamp;
    updates.transitDays = daysBetween(summary?.pickedUpAt, data.latestEvent.timestamp);
  }

  await db.shipmentSummaries.upsert(
    { trackingNumber: data.trackingNumber },
    { $set: updates }
  );
}

Building the Analytics Queries

Carrier Performance Comparison

async function getCarrierPerformance(dateRange) {
  const results = await db.shipmentSummaries.aggregate([
    {
      $match: {
        deliveredAt: { $gte: dateRange.start, $lte: dateRange.end },
      },
    },
    {
      $group: {
        _id: '$carrier',
        totalShipments: { $sum: 1 },
        avgTransitDays: { $avg: '$transitDays' },
        onTimeCount: {
          $sum: { $cond: [{ $lte: ['$transitDays', '$estimatedDays'] }, 1, 0] },
        },
        exceptionCount: {
          $sum: { $cond: ['$hasException', 1, 0] },
        },
      },
    },
    {
      $project: {
        carrier: '$_id',
        totalShipments: 1,
        avgTransitDays: { $round: ['$avgTransitDays', 1] },
        onTimeRate: {
          $round: [{ $multiply: [{ $divide: ['$onTimeCount', '$totalShipments'] }, 100] }, 1],
        },
        exceptionRate: {
          $round: [{ $multiply: [{ $divide: ['$exceptionCount', '$totalShipments'] }, 100] }, 1],
        },
      },
    },
    { $sort: { onTimeRate: -1 } },
  ]);

  return results;
}

Transit Time by Route

async function getTransitTimeByRoute(carrier, dateRange) {
  return db.shipmentSummaries.aggregate([
    {
      $match: {
        carrier,
        deliveredAt: { $gte: dateRange.start, $lte: dateRange.end },
      },
    },
    {
      $group: {
        _id: {
          origin: '$originCountry',
          destination: '$destinationCountry',
        },
        avgDays: { $avg: '$transitDays' },
        minDays: { $min: '$transitDays' },
        maxDays: { $max: '$transitDays' },
        p90Days: { $percentile: { input: '$transitDays', p: [0.9], method: 'approximate' } },
        count: { $sum: 1 },
      },
    },
    { $match: { count: { $gte: 10 } } }, // Only statistically significant routes
    { $sort: { avgDays: 1 } },
  ]);
}
async function getDailyDeliveryTrends(dateRange) {
  return db.shipmentSummaries.aggregate([
    {
      $match: {
        deliveredAt: { $gte: dateRange.start, $lte: dateRange.end },
      },
    },
    {
      $group: {
        _id: { $dateToString: { format: '%Y-%m-%d', date: '$deliveredAt' } },
        delivered: { $sum: 1 },
        avgTransitDays: { $avg: '$transitDays' },
        exceptions: { $sum: { $cond: ['$hasException', 1, 0] } },
      },
    },
    { $sort: { _id: 1 } },
  ]);
}

Dashboard Layout

Organize your dashboard into logical sections:

Overview Section

  • Total shipments (this period vs previous)
  • Average transit time trend
  • Overall on-time delivery rate
  • Active exception count

Carrier Performance Section

  • Side-by-side carrier comparison table
  • Transit time distribution chart per carrier
  • Exception rate by carrier over time

Route Analysis Section

  • Heat map of transit times by origin/destination
  • Top 10 fastest and slowest routes
  • Route-level exception patterns

Exception Monitoring Section

  • Active exceptions by type and severity
  • Exception resolution time trends
  • Carrier-specific exception patterns

Practical Insights from Analytics

Here are real patterns you might discover:

1. Carrier Selection Optimization

Carrier A: Avg 3.2 days, 94% on-time, $8.50/shipment
Carrier B: Avg 2.8 days, 91% on-time, $12.00/shipment
Carrier C: Avg 4.1 days, 97% on-time, $6.00/shipment

For time-sensitive shipments, Carrier B is fastest. For cost optimization with reliability, Carrier C offers the best value. These decisions become data-driven rather than assumption-based.

2. Route-Specific Carrier Performance

A carrier might be excellent domestically but poor internationally. Route-level analysis reveals this:

Carrier A: US → US: 2.1 days avg (excellent)
Carrier A: US → KR: 8.5 days avg (poor — competitors average 5.2 days)

3. Seasonal Patterns

Holiday seasons, monsoon periods, and major sales events all affect transit times. Historical analytics help you plan for:

  • Peak season carrier capacity
  • Customer communication during delays
  • Inventory positioning to reduce transit distances

Getting Started

  1. Set up webhook collection — Start capturing tracking events today
  2. Define your metrics — Choose 3-5 key metrics to track first
  3. Build basic queries — Carrier comparison and daily trends are the highest value
  4. Iterate — Add more dimensions as you identify patterns worth tracking

The data you need is already flowing through your tracking integration. Turning it into analytics is the step that transforms shipping from a cost center into a competitive advantage.

For setting up data collection via webhooks, see our Webhook Best Practices guide.