JavaScript

JavaScript/Node.js 택배API 연동 가이드

axios 또는 fetch를 사용한 택배 조회 및 등록
JavaScript Python Java PHP C#
이 가이드에서 다루는 내용
• axios/fetch를 사용한 HTTP 요청
• 배송 조회 API 호출
• 웹훅 수신 서버 구현 (Express.js)
• 에러 처리 패턴

1. 설치

Node.js 프로젝트에서 axios를 설치합니다.

npm install axios

또는 브라우저/Deno에서는 내장 fetch를 사용할 수 있습니다.

2. 환경 변수 설정

API 키를 환경 변수로 관리합니다.

# .env 파일
DELIVERY_PUBLIC_KEY=pk_live_your_public_key
DELIVERY_SECRET_KEY=sk_live_your_secret_key
// dotenv 사용 시
require('dotenv').config();
const PUBLIC_KEY = process.env.DELIVERY_PUBLIC_KEY;
const SECRET_KEY = process.env.DELIVERY_SECRET_KEY;
const API_KEY = `${PUBLIC_KEY}:${SECRET_KEY}`;
보안 주의
Secret Key는 절대 클라이언트(브라우저) 코드에 노출하지 마세요. 반드시 서버에서만 사용하세요.

3. 배송 조회 (axios)

기본 예제 (단건 조회)

const axios = require('axios');

const PUBLIC_KEY = process.env.DELIVERY_PUBLIC_KEY;
const SECRET_KEY = process.env.DELIVERY_SECRET_KEY;
const BASE_URL = 'https://api.deliveryapi.co.kr/v1';

async function trackDelivery(courierCode, trackingNumber) {
  try {
    const response = await axios.post(
      `${BASE_URL}/tracking/trace`,
      {
        items: [{
          courierCode,
          trackingNumber
        }]
      },
      {
        headers: {
          'Authorization': `Bearer ${PUBLIC_KEY}:${SECRET_KEY}`,
          'Content-Type': 'application/json'
        }
      }
    );

    // 첫 번째 결과 반환
    return response.data.data.results[0];
  } catch (error) {
    if (error.response) {
      console.error('API 에러:', error.response.data);
      throw new Error(error.response.data.message);
    }
    throw error;
  }
}

// 사용 예시
trackDelivery('cj', '1234567890')
  .then(result => {
    if (result.success) {
      console.log('배송 상태:', result.data.deliveryStatus);
      console.log('현재 위치:', result.data.progresses[0]?.location || '정보 없음');
    } else {
      console.error('조회 실패:', result.message);
    }
  })
  .catch(err => console.error(err));

다건 조회 (최대 100건)

async function trackMultiple(items) {
  const response = await axios.post(
    `${BASE_URL}/tracking/trace`,
    { items },
    {
      headers: {
        'Authorization': `Bearer ${PUBLIC_KEY}:${SECRET_KEY}`,
        'Content-Type': 'application/json'
      }
    }
  );

  return response.data.data.results;
}

// 사용 예시: 여러 송장 동시 조회
const items = [
  { courierCode: 'cj', trackingNumber: '123456789012' },
  { courierCode: 'lotte', trackingNumber: '987654321098' },
  { courierCode: 'hanjin', trackingNumber: '112233445566' }
];

trackMultiple(items)
  .then(results => {
    results.forEach(result => {
      if (result.success) {
        console.log(`${result.data.courierName}: ${result.data.deliveryStatus}`);
      } else {
        console.error(`조회 실패 (${result.data?.trackingNumber}): ${result.message}`);
      }
    });
  });

fetch 사용 예제

async function trackDelivery(courierCode, trackingNumber) {
  const response = await fetch('https://api.deliveryapi.co.kr/v1/tracking/trace', {
    method: 'POST',
    headers: {
      'Authorization': `Bearer ${PUBLIC_KEY}:${SECRET_KEY}`,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      items: [{
        courierCode,
        trackingNumber
      }]
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(error.message);
  }

  const data = await response.json();
  return data.data.results[0];  // 첫 번째 결과 반환
}

4. 택배사 목록 조회

async function getCouriers() {
  const response = await axios.get(
    `${BASE_URL}/tracking/couriers`,
    {
      headers: {
        'Authorization': `Bearer ${PUBLIC_KEY}:${SECRET_KEY}`
      }
    }
  );

  return response.data;
}

// 결과 예시
// [
//   { code: 'lotte', name: '롯데택배' },
//   { code: 'cj', name: 'CJ대한통운' },
//   { code: 'hanjin', name: '한진택배' },
//   ...
// ]

5. 웹훅 수신 서버 (Express.js)

배송 상태 변경 알림을 받는 웹훅 서버를 구현합니다.

const express = require('express');
const crypto = require('crypto');

const app = express();
app.use(express.json());

const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;

// 서명 검증 함수
function verifySignature(payload, signature, timestamp) {
  // timestamp 유효성 검증 (5분 허용)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - parseInt(timestamp)) > 300) return false;

  // 서명 대상: timestamp.payload
  const expected = crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(`${timestamp}.${payload}`)
    .digest('hex');

  // 헤더값: sha256={hex} → prefix 제거 후 비교
  const received = signature.replace('sha256=', '');
  return crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(received));
}

// 웹훅 엔드포인트 (raw body 필요)
app.post('/webhook/delivery', express.raw({ type: 'application/json' }), (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];

  // 서명 검증
  if (!verifySignature(req.body.toString(), signature, timestamp)) {
    return res.status(401).json({ error: 'Invalid signature' });
  }

  const { event, items } = JSON.parse(req.body.toString());

  for (const item of items) {
    switch (event) {
      case 'tracking.polled':
        console.log(`배송 상태 변경: ${item.trackingNumber}`);
        console.log(`새 상태: ${item.currentStatus}`);
        // 비즈니스 로직 처리
        break;

      case 'tracking.completed':
        console.log(`전체 배송 완료: ${item.trackingNumber}`);
        // 배송 완료 처리
        break;
    }
  }

  // 200 응답 (필수)
  res.status(200).json({ received: true });
});

app.listen(3000, () => {
  console.log('Webhook server running on port 3000');
});

6. 에러 처리

async function trackWithRetry(courier, trackingNumber, maxRetries = 3) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await trackDelivery(courier, trackingNumber);
    } catch (error) {
      // 재시도 가능한 에러인지 확인
      const isRetryable = error.response?.status >= 500
        || error.code === 'ECONNRESET';

      if (isRetryable && attempt < maxRetries) {
        // 지수 백오프
        const delay = Math.pow(2, attempt) * 1000;
        console.log(`재시도 ${attempt}/${maxRetries}, ${delay}ms 후...`);
        await new Promise(r => setTimeout(r, delay));
        continue;
      }

      throw error;
    }
  }
}

// 에러 코드별 처리
function handleApiError(error) {
  const code = error.response?.data?.code;

  switch (code) {
    case 'INVALID_TRACKING_NUMBER':
      return '잘못된 송장번호입니다';
    case 'COURIER_NOT_SUPPORTED':
      return '지원하지 않는 택배사입니다';
    case 'RATE_LIMIT_EXCEEDED':
      return '요청 한도를 초과했습니다';
    default:
      return '일시적인 오류가 발생했습니다';
  }
}

7. TypeScript 타입 정의

interface TrackingResult {
  success: boolean;
  data: {
    courierCode: string;
    courierName: string;
    trackingNumber: string;
    deliveryStatus: 'PENDING' | 'PICKED_UP' | 'IN_TRANSIT' | 'OUT_FOR_DELIVERY' | 'DELIVERED';
    deliveryStatusText: string;
    isDelivered: boolean;
    progresses: Progress[];
  };
}

interface Progress {
  dateTime: string;
  status: string;
  statusCode?: string;
  location: string;
  description: string;
}

interface Courier {
  code: string;
  name: string;
}

async function trackDelivery(
  courier: string,
  trackingNumber: string
): Promise<TrackingResult> {
  // ... 구현
}

다른 언어 가이드

지금 바로 시작하세요!

무료 플랜으로 API를 바로 테스트해보세요

무료로 시작하기 →