배송 추적 예외 처리: 개발자를 위한 엣지 케이스 가이드
이상적인 세계에서는 모든 택배가 집하부터 배송까지 순조롭게 진행됩니다. 하지만 현실에서는 전체 배송의 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 });
});
모범 사례 정리
- 예외를 심각도별로 분류 — 모든 예외에 동일한 수준으로 대응할 필요는 없습니다
- 고객 친화적 언어 사용 — 기술적 상태를 명확한 메시지로 변환하세요
- 구체적인 다음 단계 제시 — 고객이 취할 수 있는 조치가 있다면 안내하세요
- 가능한 부분은 자동화 — 자주 발생하는 예외에 대한 워크플로우를 설정하세요
- 예외 발생률 모니터링 — 택배사별, 경로별 패턴을 추적하여 시스템적 문제를 조기에 파악하세요
- 해결 SLA 설정 — 심각한 예외는 며칠이 아닌 몇 시간 내에 처리해야 합니다
- 폴링 대신 webhook 사용 — 실시간 알림으로 예외를 신속하게 처리할 수 있습니다
견고한 예외 처리 시스템은 불편한 배송 경험과 신뢰할 수 있는 배송 경험을 가르는 핵심 요소입니다. WhereParcel은 500개 이상의 택배사에 걸쳐 표준화된 예외 상태를 제공하므로, 한 번만 시스템을 구축하면 어떤 택배사를 사용하든 일관된 예외 처리가 가능합니다.
Webhook 설정에 대한 자세한 내용은 Webhook 모범 사례 가이드를 참고하세요.