"배송 어디쯤 왔나요?" — 쇼핑몰 CS 문의의 절반 이상을 차지하는 질문입니다. 배송 상태가 변경될 때마다 자동으로 카카오 알림톡이나 문자를 보내면 고객 만족도는 올라가고 CS 문의는 줄어듭니다.

💡 이 가이드에서 구축하는 시스템

• 웹훅으로 배송 상태 변경을 실시간 수신
• 상태별 맞춤 메시지 자동 생성
• 카카오 알림톡 → 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 영업일이 소요되니 미리 준비하세요.

다중 채널 발송 전략

알림톡이 실패할 수 있는 상황(수신 거부, 카카오 미가입 등)을 대비해 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. 모니터링 - 발송 실패율이 임계값 초과 시 알람

핵심 요약

✅ 배송 알림 자동화 3단계

1단계: 웹훅 구독 - 주문 생성 시 metadata와 함께 구독 등록
2단계: 상태별 분기 - 웹훅 수신 → 상태에 맞는 메시지 생성
3단계: 다중 채널 발송 - 알림톡 우선 → SMS 대체 → 로그 기록

다음 단계

지금 바로 배송 알림 자동화를 시작하세요

무료 회원가입 후 웹훅 설정까지 10분이면 충분합니다

무료로 시작하기 →