"배송 어디쯤 왔나요?" — 쇼핑몰 CS 문의의 절반 이상을 차지하는 질문입니다. 배송 상태가 변경될 때마다 자동으로 카카오 알림톡이나 문자를 보내면 고객 만족도는 올라가고 CS 문의는 줄어듭니다.
💡 이 가이드에서 구축하는 시스템
• 웹훅으로 배송 상태 변경을 실시간 수신
• 상태별 맞춤 메시지 자동 생성
• 카카오 알림톡 → SMS 순서로 다중 채널 발송
• 중복 알림 방지, 야간 발송 제한 등 프로덕션 팁
• 웹훅으로 배송 상태 변경을 실시간 수신
• 상태별 맞춤 메시지 자동 생성
• 카카오 알림톡 → SMS 순서로 다중 채널 발송
• 중복 알림 방지, 야간 발송 제한 등 프로덕션 팁
왜 배송 알림 자동화가 필요한가?
| 항목 | 수동 알림 | 자동 알림 |
|---|---|---|
| 발송 시점 | 직원이 확인 후 수동 발송 | 상태 변경 즉시 자동 발송 |
| CS 문의량 | "배송 어디예요?" 하루 30건+ | 80% 이상 감소 |
| 고객 경험 | 고객이 직접 조회해야 함 | 알림이 먼저 도착 |
| 리뷰 유도 | 별도 알림 필요 | 배송 완료 시 리뷰 링크 자동 포함 |
전체 아키텍처
택배사 → DeliveryAPI → 웹훅 → 내 서버 → 알림톡 / SMS → 고객
DeliveryAPI의 웹훅 구독 기능을 사용하면 배송 상태가 변경될 때마다 내 서버로 자동 알림이 옵니다. 이 알림을 받아서 카카오 알림톡이나 SMS로 고객에게 전달하는 구조입니다.
1웹훅 구독 설정
주문이 생성될 때 웹훅 구독을 함께 등록합니다. 핵심은 metadata에 고객 정보를 포함시키는 것입니다. 이렇게 하면 웹훅을 받았을 때 별도의 DB 조회 없이 바로 알림을 보낼 수 있습니다.
// 주문 생성 시 웹훅 구독 등록
const axios = require('axios');
async function registerTracking(order) {
const response = await axios.post(
'https://api.deliveryapi.co.kr/v1/webhooks/register',
{
endpointId: 'ep_your_endpoint_id',
items: [{
courierCode: order.courierCode,
trackingNumber: order.trackingNumber,
clientId: order.id,
}],
recurring: true,
metadata: {
customerPhone: order.customerPhone,
customerName: order.customerName,
productName: order.productName,
}
},
{
headers: {
'Authorization': `Bearer ${API_KEY}:${API_SECRET}`,
'Content-Type': 'application/json'
}
}
);
console.log('웹훅 구독 등록 완료:', response.data);
}
// 사용 예시
registerTracking({
id: 'ORD-20260221-001',
courierCode: 'cj',
trackingNumber: '123456789012',
customerPhone: '01012345678',
customerName: '홍길동',
productName: '제주 감귤 5kg',
}); ✅ metadata 활용 팁
metadata에 고객 전화번호, 주문번호, 상품명을 포함하면 웹훅 수신 시 별도 DB 조회가 필요 없습니다. API 호출 한 번을 줄이는 것만으로도 처리 속도가 크게 향상됩니다.
2웹훅 수신 서버 구현
웹훅 핸들러
서명 검증으로 보안을 확보하고, 상태에 따라 알림을 분기합니다:
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());
// 웹훅 수신 엔드포인트
app.post('/api/webhook/delivery', async (req, res) => {
// 1. 서명 검증 (보안)
const signature = req.headers['x-webhook-signature'];
const timestamp = req.headers['x-webhook-timestamp'];
const expectedSignature = crypto
.createHmac('sha256', process.env.WEBHOOK_SECRET)
.update(`${timestamp}.${JSON.stringify(req.body)}`)
.digest('hex');
if (signature !== `sha256=${expectedSignature}`) {
return res.status(401).json({ message: 'Invalid signature' });
}
// 2. 웹훅 데이터 추출
const { event, items, metadata } = req.body;
// 3. 각 항목별 알림 발송
for (const item of items) {
const { trackingNumber, currentStatus, isDelivered, trackingData } = item;
const deliveryStatusText = trackingData?.deliveryStatusText ?? currentStatus;
console.log(`[${trackingNumber}] 상태 변경: ${deliveryStatusText}`);
try {
await sendNotification({
status: currentStatus,
statusText: deliveryStatusText,
isDelivered,
metadata, // 구독 시 등록한 메타데이터
});
} catch (error) {
console.error('알림 발송 실패:', error);
}
}
// 4. 빠른 응답 (타임아웃 방지)
res.status(200).json({ received: true });
}); 상태별 메시지 템플릿
배송 상태에 따라 고객에게 보낼 메시지를 미리 정의합니다. 배송 완료 시에는 리뷰 작성 링크를 포함하여 자연스럽게 리뷰를 유도할 수 있습니다:
| 배송 상태 | 알림 제목 | 활용 |
|---|---|---|
PICKED_UP | 상품이 발송되었습니다 | 택배사, 송장번호, 조회 링크 |
IN_TRANSIT | 상품이 이동 중입니다 | 현재 위치 안내 |
OUT_FOR_DELIVERY | 곧 도착합니다! | 당일 도착 예정 안내 |
DELIVERED | 배송이 완료되었습니다 | 리뷰 작성 유도 링크 포함 |
const MESSAGE_TEMPLATES = {
PICKED_UP: {
title: '상품이 발송되었습니다',
body: (data) =>
`[${data.productName}] 상품이 발송되었습니다.`
+ `\n택배사: ${data.courierName}`
+ `\n송장번호: ${data.trackingNumber}`
+ `\n배송조회: https://my-shop.com/tracking/${data.orderId}`,
},
IN_TRANSIT: {
title: '상품이 이동 중입니다',
body: (data) =>
`[${data.productName}] 상품이 배송 중입니다.`
+ `\n현재 위치: ${data.location}`
+ `\n배송조회: https://my-shop.com/tracking/${data.orderId}`,
},
OUT_FOR_DELIVERY: {
title: '곧 도착합니다!',
body: (data) =>
`[${data.productName}] 배송 출발했습니다.`
+ `\n오늘 중 도착 예정입니다.`
+ `\n배송조회: https://my-shop.com/tracking/${data.orderId}`,
},
DELIVERED: {
title: '배송이 완료되었습니다',
body: (data) =>
`[${data.productName}] 배송이 완료되었습니다.`
+ `\n수령 확인 후 리뷰를 남겨주세요!`
+ `\n리뷰 작성: https://my-shop.com/review/${data.orderId}`,
},
}; 3알림 발송 채널 연동
카카오 알림톡 발송
카카오 알림톡은 도달률이 가장 높은 채널입니다. Solapi, NHN Cloud 등의 메시징 서비스를 통해 발송할 수 있습니다:
const axios = require('axios');
// Solapi (솔라피) 카카오 알림톡 발송 예시
async function sendAlimtalk(phone, templateId, variables) {
try {
const response = await axios.post(
'https://api.solapi.com/messages/v4/send',
{
message: {
to: phone,
from: process.env.SENDER_PHONE,
kakaoOptions: {
pfId: process.env.KAKAO_PF_ID, // 카카오 채널 ID
templateId: templateId, // 사전 승인된 템플릿 ID
variables: variables, // 템플릿 변수
}
}
},
{
headers: {
'Authorization': `Bearer ${process.env.SOLAPI_API_KEY}`,
}
}
);
return { success: true, messageId: response.data.groupId };
} catch (error) {
console.error('알림톡 발송 실패:', error.message);
return { success: false, error: error.message };
}
} ⚠️ 카카오 알림톡 사전 준비
알림톡을 발송하려면 카카오톡 채널 개설과 메시지 템플릿 사전 승인이 필요합니다. 승인에 1~3 영업일이 소요되니 미리 준비하세요.
알림톡을 발송하려면 카카오톡 채널 개설과 메시지 템플릿 사전 승인이 필요합니다. 승인에 1~3 영업일이 소요되니 미리 준비하세요.
다중 채널 발송 전략
알림톡이 실패할 수 있는 상황(수신 거부, 카카오 미가입 등)을 대비해 SMS를 대체 채널로 설정합니다:
async function sendNotification({ status, statusText, isDelivered, metadata }) {
const template = MESSAGE_TEMPLATES[status];
// 알림을 보내지 않는 상태는 무시
if (!template) return;
const messageData = {
productName: metadata.productName,
courierName: metadata.courierName || '',
trackingNumber: metadata.trackingNumber || '',
orderId: metadata.orderId,
location: metadata.lastLocation || '',
};
const message = template.body(messageData);
const phone = metadata.customerPhone;
// 1순위: 카카오 알림톡
const alimtalkResult = await sendAlimtalk(
phone,
`DELIVERY_${status}`, // 템플릿 ID
messageData
);
if (alimtalkResult.success) {
console.log(`알림톡 발송 성공: ${phone}`);
await saveLog({ phone, channel: 'alimtalk', status, success: true });
return;
}
// 2순위: SMS 문자 (알림톡 실패 시)
console.log('알림톡 실패, SMS로 전환');
const smsResult = await sendSms(phone, message);
await saveLog({
phone,
channel: smsResult.success ? 'sms' : 'failed',
status,
success: smsResult.success,
});
} 프로덕션 배포 시 고려사항
중복 알림 방지
네트워크 문제로 동일한 웹훅이 여러 번 올 수 있습니다. Redis를 이용하면 간단하게 중복을 방지할 수 있습니다:
// Redis를 이용한 중복 알림 방지
const Redis = require('ioredis');
const redis = new Redis(process.env.REDIS_URL);
async function isDuplicate(trackingNumber, status) {
const key = `notification:${trackingNumber}:${status}`;
const exists = await redis.get(key);
if (exists) {
console.log(`중복 알림 차단: ${trackingNumber} - ${status}`);
return true;
}
// 24시간 동안 중복 체크 유지
await redis.set(key, '1', 'EX', 86400);
return false;
}
// 웹훅 핸들러에서 사용
app.post('/api/webhook/delivery', async (req, res) => {
const { items } = req.body;
for (const item of items) {
const { trackingNumber, currentStatus } = item;
// 중복 체크
if (await isDuplicate(trackingNumber, currentStatus)) {
continue;
}
// 알림 발송 로직...
}
res.status(200).json({ received: true });
}); 야간 알림 제한
밤 9시 이후부터 아침 8시까지는 광고성 메시지 발송이 제한됩니다. 배송 완료처럼 고객이 기다리는 정보는 예외로 처리합니다:
function isNightTime() {
const hour = new Date().getHours();
return hour >= 21 || hour < 8; // 밤 9시 ~ 아침 8시
}
async function sendNotificationSafe(data) {
if (isNightTime() && !data.isDelivered) {
// 배송 완료가 아닌 경우 야간 알림 보류
await saveForMorning(data); // 아침 8시에 발송하도록 큐에 저장
console.log('야간 시간대 - 아침 발송 예약');
return;
}
// 배송 완료 알림은 야간에도 발송 (고객이 기다리는 정보)
await sendNotification(data);
} 💡 프로덕션 체크리스트
1. 중복 방지 - Redis/Set으로 동일 상태 알림 차단
2. 야간 제한 - 21시~08시 비필수 알림 보류
3. 발송 로그 - 모든 알림의 성공/실패를 DB에 기록
4. 재시도 로직 - 일시적 실패 시 지수 백오프 재시도
5. 모니터링 - 발송 실패율이 임계값 초과 시 알람
1. 중복 방지 - Redis/Set으로 동일 상태 알림 차단
2. 야간 제한 - 21시~08시 비필수 알림 보류
3. 발송 로그 - 모든 알림의 성공/실패를 DB에 기록
4. 재시도 로직 - 일시적 실패 시 지수 백오프 재시도
5. 모니터링 - 발송 실패율이 임계값 초과 시 알람
핵심 요약
✅ 배송 알림 자동화 3단계
1단계: 웹훅 구독 - 주문 생성 시 metadata와 함께 구독 등록
2단계: 상태별 분기 - 웹훅 수신 → 상태에 맞는 메시지 생성
3단계: 다중 채널 발송 - 알림톡 우선 → SMS 대체 → 로그 기록
1단계: 웹훅 구독 - 주문 생성 시 metadata와 함께 구독 등록
2단계: 상태별 분기 - 웹훅 수신 → 상태에 맞는 메시지 생성
3단계: 다중 채널 발송 - 알림톡 우선 → SMS 대체 → 로그 기록
다음 단계
- 웹훅 구독 API 가이드 - 웹훅 구독 API 상세 문서
- 쇼핑몰 배송조회 페이지 만들기 - 고객용 배송조회 UI 구축
- 엑셀로 1,000건 배송 등록하기 - 대량 주문 처리
- API 에러 처리 베스트 프랙티스 - 안정적인 서비스 구축
- 전체 API 문서 - 상세한 API 레퍼런스