배송 추적 예외 처리: 개발자를 위한 엣지 케이스 가이드

이상적인 세계에서는 모든 택배가 집하부터 배송까지 순조롭게 진행됩니다. 하지만 현실에서는 전체 배송의 5~15%가 예외 상황을 겪습니다 — 배송 실패, 통관 보류, 파손, 주소 오류 등이 대표적입니다. 애플리케이션이 이러한 예외를 어떻게 처리하느냐에 따라 고객이 배송 경험을 신뢰하느냐, 아니면 고객센터에 문의를 쏟아내느냐가 결정됩니다.

일반적인 추적 예외 유형

예외 상황의 종류를 이해하면 각각에 적합한 처리 방식을 설계할 수 있습니다:

배송 예외

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

주요 배송 예외 유형:

  • 배송 시도 실패 — 부재중, 영업시간 종료
  • 접근 불가 — 게이트 단지, 잠긴 건물
  • 수취 거부 — 수취인이 택배 수령을 거부
  • 주소 오류 — 주소 불완전 또는 잘못된 주소

통관 예외

국제 배송에서는 통관 관련 보류가 빈번하게 발생합니다:

  • 서류 미비 — 상업 송장이나 통관 서류 누락
  • 검사 대상 — 물리적 검사 대상으로 선정
  • 관세 미납 — 수취인이 수입 관세를 납부해야 함
  • 금지 품목 — 목적지 국가에서 반입이 제한된 물품

운송 예외

  • 기상 지연 — 악천후로 인한 운송 차질
  • 택배사 지연 — 분류 시설 적체, 차량 문제
  • 오분류 — 잘못된 시설로 배송됨
  • 파손 — 운송 중 택배 파손

예외 처리 시스템 구축

1단계: 분류 및 우선순위 설정

모든 예외가 동일한 대응을 필요로 하는 것은 아닙니다. 우선순위 체계를 구축하세요:

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;
  }
}

2단계: 고객 친화적 메시지 작성

기술적인 추적 상태는 고객에게 혼란을 줍니다. 명확하고 실행 가능한 메시지로 변환하세요:

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',
    },
  },
};

3단계: 자동 복구 워크플로우

일부 예외 상황은 자동으로 해결할 수 있습니다:

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;
    }
  }
}

예외 발생률 모니터링

예외 패턴을 추적하여 시스템적 문제를 조기에 발견하세요:

// 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;
}

알림 임계값 설정

예외 발생률에 대한 알림 기준을 정의합니다:

예외 유형경고 임계값심각 임계값
배송 실패> 10%> 20%
통관 보류> 15% (국제)> 30% (국제)
파손> 1%> 3%
분실> 0.5%> 1%
주소 오류> 5%> 10%

Webhook 기반 예외 처리

WhereParcel webhook을 사용하여 실시간으로 예외 이벤트를 수신합니다:

// 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 });
});

모범 사례 정리

  1. 예외를 심각도별로 분류 — 모든 예외에 동일한 수준으로 대응할 필요는 없습니다
  2. 고객 친화적 언어 사용 — 기술적 상태를 명확한 메시지로 변환하세요
  3. 구체적인 다음 단계 제시 — 고객이 취할 수 있는 조치가 있다면 안내하세요
  4. 가능한 부분은 자동화 — 자주 발생하는 예외에 대한 워크플로우를 설정하세요
  5. 예외 발생률 모니터링 — 택배사별, 경로별 패턴을 추적하여 시스템적 문제를 조기에 파악하세요
  6. 해결 SLA 설정 — 심각한 예외는 며칠이 아닌 몇 시간 내에 처리해야 합니다
  7. 폴링 대신 webhook 사용 — 실시간 알림으로 예외를 신속하게 처리할 수 있습니다

견고한 예외 처리 시스템은 불편한 배송 경험과 신뢰할 수 있는 배송 경험을 가르는 핵심 요소입니다. WhereParcel은 500개 이상의 택배사에 걸쳐 표준화된 예외 상태를 제공하므로, 한 번만 시스템을 구축하면 어떤 택배사를 사용하든 일관된 예외 처리가 가능합니다.

Webhook 설정에 대한 자세한 내용은 Webhook 모범 사례 가이드를 참고하세요.