🔔 웹훅 구독 API 가이드 - 택배 상태 변경 자동 알림
💡 이 가이드에서 배울 내용: 택배 상태가 변경될 때마다 자동으로 웹훅을 받는 방법, 웹훅 URL 등록부터 보안 검증까지 전체 플로우를 단계별로 설명합니다.
개요
웹훅 구독 API를 사용하면 택배사 송장번호를 등록하고, 배송 상태가 변경될 때마다 자동으로 알림을 받을 수 있습니다. 폴링(Polling) 방식 대신 이벤트 기반으로 알림을 받으므로 서버 리소스를 절약하고 실시간으로 배송 정보를 업데이트할 수 있습니다.
주요 특징
- 자동 폴링: 1시간 고정 주기 (22:00~07:00 KST 야간 중단)
- 매 폴링마다 전송: 변경 여부와 무관하게 매 사이클 전송되며,
items[].hasChanged로 실제 변경 여부를 구분 - Dual ID 지원: 서버 생성 ID 또는 클라이언트 지정 ID로 관리
- 보안 서명: HMAC-SHA256 서명으로 웹훅 검증
폴링 정책
폴링 주기
모든 활성 상태에 대해 1시간 주기로 폴링합니다.
| 배송 상태 | 폴링 주기 | 설명 |
|---|---|---|
| PENDING, REGISTERED, PICKUP_READY, PICKED_UP, IN_TRANSIT, OUT_FOR_DELIVERY | 1시간 | 활성 상태 — 주기적 폴링 |
| DELIVERED | 중지 | 구독 자동 완료 |
| FAILED, RETURNED, CANCELLED, HOLD | 1시간 | 예외 상태 — 배송완료로 전환될 수 있어 14일 만료 전까지 계속 폴링 |
야간 중단 (KST 기준)
- 주간 (07:00~22:00): 1시간 주기로 폴링
- 야간 (22:00~07:00): 폴링 일시 중단 — 택배사 시스템 유지보수 시간대
14일 자동 중단
구독 등록 후 14일이 경과하면 자동으로 폴링이 중단됩니다. 장기간 배송되지 않는 건에 대한 불필요한 조회를 방지하기 위한 정책입니다.
등록일: 2026-01-01
중단일: 2026-01-15 (14일 후)
14일째부터는 폴링이 더 이상 수행되지 않습니다.
1단계: 웹훅 URL 사전 등록
구독을 등록하기 전에, 알림을 받을 웹훅 URL을 먼저 등록해야 합니다. API Key당 최대 10개까지 등록 가능합니다.
요청 예시
curl -X POST "https://api.deliveryapi.co.kr/v1/webhooks/endpoints" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer __API_KEY__:__SECRET_KEY__" \
-d '{
"url": "https://your-domain.com/webhook",
"name": "주문 시스템"
}' 응답 예시
{
"isSuccess": true,
"data": {
"endpointId": "ep_6b78d0e9eac154e129847aae",
"url": "https://your-domain.com/webhook",
"name": "주문 시스템",
"webhookSecret": "whsec_91289c5160ec743e0721b4a23fb5d33c",
"dateCreated": "2026-01-13T10:30:33.292Z"
}
} ⚠️ 중요: webhookSecret은 최초 등록 시 딱 1회만 반환됩니다. 반드시 안전한 곳에 저장하세요. 이 값은 웹훅 서명 검증에 사용됩니다.
2단계: 구독 등록
등록한 웹훅 URL의 endpointId를 사용하여 송장번호를 구독합니다. 1회 요청으로 최대 100건까지 일괄 등록 가능합니다.
요청 예시
curl -X POST "https://api.deliveryapi.co.kr/v1/webhooks/register" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer __API_KEY__:__SECRET_KEY__" \
-d '{
"endpointId": "ep_6b78d0e9eac154e129847aae",
"items": [
{
"courierCode": "cj",
"trackingNumber": "123456789012",
"clientId": "order-001"
}
],
"recurring": true,
"metadata": { "orderId": "ORD-001" }
}' 요청 필드 설명
| 필드 | 필수 | 설명 |
|---|---|---|
endpointId | 선택 | 사전 등록한 웹훅 URL의 ID. 미제공 시 웹훅 없이 크롤 후 GET으로 조회 |
items[].courierCode | 필수 | 택배사 코드 (예: cj, hanjin, lotte) |
items[].trackingNumber | 필수 | 송장번호 |
items[].clientId | 선택 | 클라이언트 매핑용 ID (예: 주문번호). 웹훅 발송 시 함께 전달됨 |
recurring | 필수 | true: 14일 구독형 (상태 변경마다 웹훅), false: 즉시 1회 크롤 |
metadata | 선택 | 추가 정보 (JSON 객체). 웹훅 발송 시 함께 전달됨 |
응답 예시
{
"isSuccess": true,
"data": {
"requestId": "req_cb0d4e05b5ca97f777b72215",
"itemCount": 1,
"recurring": true
},
"message": "추적 요청이 등록되었습니다. 결과는 웹훅으로 수신하거나 requestId로 조회할 수 있습니다."
} ✅ requestId: 응답의 requestId를 저장하세요. 이후 구독 상세 조회(GET /v1/webhooks/subscriptions/{requestId})나 취소(DELETE) 시 사용합니다.
3단계: 웹훅 수신 및 검증
구독이 활성화된 동안 폴링 주기(1시간)마다 등록한 URL로 웹훅이 전송됩니다. 변경 여부와 무관하게 매번 전송되며, items[].hasChanged로 실제 변경 여부를 구분할 수 있습니다.
웹훅 페이로드
{
"event": "tracking.polled",
"requestId": "req_cb0d4e05b5ca97f777b72215",
"timestamp": "2026-01-13T10:30:00.000Z",
"summary": {
"total": 1,
"delivered": 1,
"active": 0,
"hasChanges": true
},
"items": [
{
"courierCode": "cj",
"trackingNumber": "123456789012",
"clientId": "order-001",
"previousStatus": "IN_TRANSIT",
"currentStatus": "DELIVERED",
"hasChanged": true,
"isDelivered": true,
"trackingData": {
"courierCode": "cj",
"courierName": "CJ대한통운",
"trackingNumber": "123456789012",
"deliveryStatus": "DELIVERED",
"deliveryStatusText": "배송완료",
"isDelivered": true,
"progresses": [...]
}
}
],
"metadata": { "orderId": "ORD-001" }
} 보안 서명 검증 (Node.js)
const crypto = require('crypto');
// rawBody는 express.raw({ type: 'application/json' })로 받은 원본 문자열이어야 합니다.
// express.json()으로 미리 파싱한 뒤 JSON.stringify로 재구성하면 서버가 서명한
// 원본 바이트와 달라질 수 있어 검증이 실패할 수 있습니다.
function verifyWebhookSignature(rawBody, headers, webhookSecret) {
const signature = headers['x-webhook-signature'];
const timestamp = headers['x-webhook-timestamp'];
// 타임스탬프 검증 (5분 이내)
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp, 10)) > 300) {
return false;
}
// HMAC 서명 계산
const payload = `${timestamp}.${rawBody}`;
const expectedSignature = crypto
.createHmac('sha256', webhookSecret)
.update(payload)
.digest('hex');
const expected = Buffer.from(`sha256=${expectedSignature}`);
const received = Buffer.from(signature || '');
return (
expected.length === received.length &&
crypto.timingSafeEqual(expected, received)
);
} 4단계: 구독 관리
구독 목록 조회
curl "https://api.deliveryapi.co.kr/v1/webhooks/subscriptions?status=active&limit=20" \
-H "Authorization: Bearer __API_KEY__:__SECRET_KEY__" 구독 상세 조회
# requestId로 조회
curl "https://api.deliveryapi.co.kr/v1/webhooks/subscriptions/req_cb0d4e05b5ca97f777b72215" \
-H "Authorization: Bearer __API_KEY__:__SECRET_KEY__" 구독 취소
curl -X DELETE "https://api.deliveryapi.co.kr/v1/webhooks/subscriptions/req_cb0d4e05b5ca97f777b72215" \
-H "Authorization: Bearer __API_KEY__:__SECRET_KEY__" 폴링 주기 정책
구독한 송장은 1시간 주기로 자동 조회됩니다. 야간(22:00~07:00 KST)에는 폴링이 일시 중단됩니다.
| 상태 | 폴링 주기 | 설명 |
|---|---|---|
| 활성 (PENDING, REGISTERED, IN_TRANSIT 등) | 1시간 | 주기적으로 상태 확인 |
| 배송 완료 (DELIVERED) | 중지 | 구독 자동 완료 |
| 예외 (FAILED, RETURNED, CANCELLED, HOLD) | 1시간 | 14일 만료 전까지 계속 폴링 |
실패 처리
별도의 재시도 큐는 없습니다. 웹훅 전송과 택배사 조회 모두 폴링 주기(1시간)마다 한 번씩 수행되며, 실패해도 다음 폴링 사이클에 자동으로 다시 시도됩니다.
웹훅 전송 실패 시
전송이 실패하면 실패 이력만 기록하고, 다음 폴링 사이클(1시간 후)에 다시 전송을 시도합니다. 특정 엔드포인트가 계속 실패 응답을 반환하면 자동으로 비활성화될 수 있습니다.
택배 조회 실패 시 (크롤러 오류)
별도의 지수 백오프는 없습니다. 조회에 실패한 항목은 다음 정기 폴링(1시간 후)에 다시 조회됩니다.
⚠️ 중요: 웹훅 전송 실패와 택배 조회 실패는 별도로 기록됩니다. 웹훅만 실패해도 구독은 계속 유지되며, 다음 폴링 사이클에 다시 웹훅을 시도합니다.
베스트 프랙티스
- Idempotency: 같은 이벤트가 중복 전송될 수 있으므로,
requestId+timestamp로 중복 체크 - 빠른 응답: 웹훅 수신 시 즉시 200 응답 후 비동기로 처리 (3초 이내 응답 권장)
- 서명 검증: 모든 웹훅에 대해 HMAC 서명 검증 필수
- Client ID 활용: 자체 주문번호를
clientId로 사용하면 별도 매핑 테이블 불필요 - recurring 활용: 실시간 추적이 필요하면
recurring: true(14일 구독), 한 번만 확인하면recurring: false
테스트 방법
- webhook.site에서 무료 테스트 URL 생성
- 생성된 URL로 웹훅 엔드포인트 등록
- 실제 송장번호로 구독 등록
- webhook.site에서 실시간으로 웹훅 수신 확인
- 웹훅 테스트 페이지에서 직접 테스트 가능
다음 단계
🎉 준비 완료! 이제 실시간 택배 알림 시스템을 구축할 수 있습니다. 궁금한 점은 문의하기로 연락주세요.