Handling Tracking Exceptions: A Developer’s Guide to Edge Cases

In a perfect world, every package would move smoothly from pickup to delivery. In reality, 5-15% of shipments encounter exceptions — failed deliveries, customs holds, damaged packages, or address problems. How your application handles these exceptions determines whether customers trust your shipping experience or flood your support channels.

Common Tracking Exception Types

Understanding exception categories helps you build appropriate handling for each:

Delivery Exceptions

{
  "status": "delivery_exception",
  "substatus": "failed_attempt",
  "description": "Delivery attempted - no one available to sign",
  "timestamp": "2026-01-18T14:30:00Z",
  "location": "Seoul, KR"
}

Common delivery exceptions include:

  • Failed attempt — Nobody home, business closed
  • Access issue — Gated community, locked building
  • Refused — Recipient refused the package
  • Incorrect address — Address incomplete or invalid

Customs Exceptions

International shipments frequently encounter customs-related holds:

  • Documentation required — Missing commercial invoice or customs forms
  • Inspection — Package selected for physical inspection
  • Duties pending — Recipient needs to pay import duties
  • Prohibited item — Contents restricted in destination country

Transit Exceptions

  • Weather delay — Severe weather affecting transportation
  • Carrier delay — Sorting facility backlog, vehicle issues
  • Missort — Package routed to wrong facility
  • Damaged — Package damaged during transit

Building an Exception Handling System

Step 1: Categorize and Prioritize

Not all exceptions require the same response. Build a priority system:

const EXCEPTION_PRIORITY = {
  // Critical — requires immediate customer notification
  'damaged': 'critical',
  'lost': 'critical',
  'refused': 'critical',

  // High — customer action may be needed
  'customs_hold': 'high',
  'duties_pending': 'high',
  'incorrect_address': 'high',
  'documentation_required': 'high',

  // Medium — informational, carrier will retry
  'failed_attempt': 'medium',
  'access_issue': 'medium',
  'weather_delay': 'medium',

  // Low — temporary, usually resolves automatically
  'carrier_delay': 'low',
  'missort': 'low',
};

function handleException(event) {
  const priority = EXCEPTION_PRIORITY[event.substatus] || 'medium';

  switch (priority) {
    case 'critical':
      notifyCustomerImmediately(event);
      createSupportTicket(event);
      break;
    case 'high':
      notifyCustomerWithAction(event);
      break;
    case 'medium':
      updateTrackingPage(event);
      break;
    case 'low':
      logAndMonitor(event);
      break;
  }
}

Step 2: Customer-Friendly Messaging

Technical tracking statuses confuse customers. Map them to clear, actionable messages:

const CUSTOMER_MESSAGES = {
  'failed_attempt': {
    title: 'Delivery attempted',
    message: 'The carrier tried to deliver your package but was unable to. They will try again on the next business day.',
    action: null,
  },
  'customs_hold': {
    title: 'Package in customs',
    message: 'Your package is being processed by customs. This typically takes 1-5 business days.',
    action: null,
  },
  'duties_pending': {
    title: 'Action required: Import duties',
    message: 'Your package requires import duty payment before it can be released from customs.',
    action: {
      label: 'Pay duties',
      url: '/shipments/{trackingNumber}/duties',
    },
  },
  'incorrect_address': {
    title: 'Address issue',
    message: 'The carrier was unable to deliver due to an address issue. Please verify your shipping address.',
    action: {
      label: 'Update address',
      url: '/shipments/{trackingNumber}/address',
    },
  },
};

Step 3: Automated Recovery Workflows

Some exceptions can be resolved automatically:

async function attemptAutoRecovery(shipment, exception) {
  switch (exception.substatus) {
    case 'failed_attempt': {
      const attempts = await getDeliveryAttempts(shipment.trackingNumber);
      if (attempts.length >= 3) {
        // After 3 failed attempts, offer pickup or redirect
        await notifyCustomer(shipment, {
          message: 'Multiple delivery attempts failed',
          options: ['Schedule redelivery', 'Pick up at facility', 'Redirect to new address'],
        });
      }
      // Otherwise, carrier will retry automatically
      break;
    }

    case 'incorrect_address': {
      // Check if we have an alternative address on file
      const altAddress = await getAlternativeAddress(shipment.recipientId);
      if (altAddress) {
        await requestAddressCorrection(shipment, altAddress);
      } else {
        await requestCustomerAddressUpdate(shipment);
      }
      break;
    }
  }
}

Monitoring Exception Rates

Track exception patterns to identify systemic issues:

// Daily exception rate monitoring
async function generateExceptionReport() {
  const today = new Date();
  const shipments = await getShipmentsForDate(today);

  const report = {
    total: shipments.length,
    exceptions: {},
    carrierBreakdown: {},
  };

  for (const shipment of shipments) {
    if (shipment.hasException) {
      const type = shipment.exceptionType;
      report.exceptions[type] = (report.exceptions[type] || 0) + 1;

      const carrier = shipment.carrier;
      if (!report.carrierBreakdown[carrier]) {
        report.carrierBreakdown[carrier] = { total: 0, exceptions: 0 };
      }
      report.carrierBreakdown[carrier].exceptions++;
    }
  }

  return report;
}

Setting Up Alerts

Define thresholds for exception rate alerts:

Exception TypeWarning ThresholdCritical Threshold
Failed delivery> 10%> 20%
Customs hold> 15% (intl.)> 30% (intl.)
Damaged> 1%> 3%
Lost> 0.5%> 1%
Address issue> 5%> 10%

Webhook-Based Exception Processing

Use WhereParcel webhooks to receive exception events in real time:

// Webhook handler for tracking exceptions
app.post('/webhooks/tracking', async (req, res) => {
  const event = req.body;

  if (event.type === 'tracking.updated') {
    const { status, substatus } = event.data.latestEvent;

    if (isException(status)) {
      await handleException(event.data);

      // Log for analytics
      await logException({
        trackingNumber: event.data.trackingNumber,
        carrier: event.data.carrier,
        exceptionType: substatus,
        timestamp: event.data.latestEvent.timestamp,
      });
    }
  }

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

Best Practices Summary

  1. Categorize exceptions by severity — Not all exceptions need the same response level
  2. Use customer-friendly language — Translate technical statuses into clear messages
  3. Provide actionable next steps — Tell customers what they can do, if anything
  4. Automate where possible — Set up workflows for common exceptions
  5. Monitor exception rates — Track patterns by carrier and route to catch systemic issues early
  6. Set SLAs for resolution — Critical exceptions should be addressed within hours, not days
  7. Use webhooks, not polling — Real-time notification ensures timely exception handling

Building robust exception handling is what separates a frustrating shipping experience from a trustworthy one. With WhereParcel’s standardized exception statuses across 500+ carriers, you can build this system once and handle exceptions consistently regardless of which carrier is involved.

For more on webhook setup, see our Webhook Best Practices guide.