← 관리 화면으로

연동 가이드 · KCP 허브 SMS

SMS 발송 API 연동

다른 서비스에서 이 서버로 문자를 보내는 방법입니다. HTTP 호출만 하면 되고, KCP 연동·발신번호 등록·한글 인코딩은 이쪽에서 처리합니다.

POST https://sms.koax.site
/api/v1/messages
먼저 읽을 것

이 두 가지를 모르면 고객에게 문자가 두 번 갑니다.

  • 502 응답 중 status: "unknown" 은 발송 여부를 알 수 없는 상태입니다. 자동 재시도 로직에서 반드시 제외하세요.
  • refId 에는 중복 방지 기능이 없습니다. 같은 값으로 두 번 보내면 두 건이 발송됩니다.

01시작하기

  1. SMS 관리자에게 API 키 발급을 요청합니다. 키에는 서비스 이름이 붙고, 발송 이력에 그 이름이 남습니다.
  2. 받은 키를 서버 환경변수에 둡니다. 코드·저장소에 넣지 마세요.
  3. 아래 발송으로 호출합니다.

키는 발급 시 한 번만 표시됩니다. 잃어버리면 재발급받아야 합니다. 유출되면 곧바로 발송 비용으로 이어지므로 관리자에게 알려 비활성화하세요.

02인증

모든 요청에 헤더를 넣습니다.

Authorization: Bearer sms_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx

헤더가 없거나, 키가 틀렸거나, 관리자가 키를 비활성화했으면 401 입니다.

03발송

필드필수설명
to필수수신번호. 하이픈·공백은 알아서 제거합니다. 010 · 011 · 016 · 017 · 018 · 019
content필수본문. EUC-KR 기준 1~80바이트 (→ 7번)
from—발신번호. 생략하면 서버 기본 발신번호를 씁니다. 지정하려면 KCP에 사전등록된 번호여야 합니다
refId—호출한 쪽에서 쓰는 참조값. 64자 이하. 응답과 조회에 그대로 돌려줍니다

성공 응답 (201)

{
  "message": {
    "id": 12,
    "status": "success",
    "to": "01012345678",
    "from": "0212345678",
    "content": "[예약안내] 9월 16일 14시에 뵙겠습니다.",
    "refId": "booking-1234",
    "resCd": "0000",
    "resMsg": "정상처리",
    "messageId": "352531",
    "jubsuNo": "260916386532",
    "createdAt": "2026-09-16T05:00:00.000Z",
    "completedAt": "2026-09-16T05:00:01.230Z"
  }
}

시간은 UTC ISO 8601 입니다. 화면에 표시할 때 현지 시간대로 바꾸세요.

04응답 상태와 재발송 규칙

이 절이 이 문서에서 가장 중요합니다. 재발송 판단을 잘못하면 고객에게 문자가 두 번 갑니다.

HTTPmessage.status뜻재발송
201success접수 완료 (resCd=0000)불필요
400—입력 검증 실패안전
401—키 문제안전
429—한도 초과안전
502failedKCP가 거절 (미발송 확정)안전
502unknown발송 여부 불명그냥 재시도 금지

400 · 401 · 429 와 502 failed 는 KCP에 요청이 나가기 전이거나 거절이 확정된 경우라 문자가 나가지 않았습니다.

502 unknown 처리 절차

unknown 은 타임아웃이나 통신 중 예외라 이미 발송됐을 수 있습니다. 이때는

  1. 그 건의 message.id 를 기록해 두고
  2. 잠시 뒤 GET /api/v1/messages/{id} 로 상태가 바뀌었는지 확인하거나
  3. SMS 관리자에게 KCP 발송 기록 확인을 요청한 뒤

재발송 여부를 정하세요. error 문구가 아니라 message.status 값으로 분기해야 합니다.

502 응답 형태

{
  "error": "발송 여부를 확인할 수 없습니다.",
  "message": { "id": 13, "status": "unknown", "resCd": "BR90", "resMsg": "...", "...": "..." }
}

05조회

목록

GET /api/v1/messages?page=1&pageSize=20&status=success&to=01012345678
파라미터기본값설명
page1
pageSize20최대 100
status전체pending · success · failed · unknown
to전체수신번호 부분 일치 (하이픈·공백 무시)
{
  "messages": [ { "id": 12, "status": "success", "...": "..." } ],
  "page": 1, "pageSize": 20, "total": 1, "totalPages": 1
}

단건

GET /api/v1/messages/12

없으면 404, id 가 정수가 아니면 400 입니다.

자기 API 키로 보낸 건만 조회됩니다. 다른 서비스가 보낸 건이나 관리 화면에서 보낸 건은 보이지 않습니다.

06오류 코드

HTTP본문원인
400{ error }JSON 파싱 실패, 번호 형식 오류, 본문 길이·문자 오류
401{ error }키 없음·오류·비활성
404{ error }조회 대상 없음
429{ error }한도 초과
502{ error, message }KCP 거절 또는 발송 불명

resCd 가 BR 로 시작하면 KCP가 아니라 이쪽 브리지가 낸 오류입니다.

resCd뜻발송
BR01필수값 누락미발송
BR02EUC-KR로 표현할 수 없는 문자미발송
BR03본문 80바이트 초과미발송
BR90브리지 실행 실패·타임아웃불명
BR99브리지 예외불명

그 외 4자리 코드는 KCP가 돌려준 값이며 resMsg 에 사유가 들어 있습니다.

07본문 제약

본문은 EUC-KR 기준 80바이트까지입니다. 영문·숫자·기본 기호가 1바이트, 한글·한자·전각 기호가 2바이트입니다. 즉 한글만 쓰면 40자, 영문만 쓰면 80자가 상한입니다.

흔한 실수

JavaScript의 str.length 로 재면 안 됩니다. 한글이 1로 세어져 통과시킨 뒤 서버에서 400 을 맞습니다. 아래에서 직접 확인해 보세요.

0 / 80 바이트 0자

보낼 수 없는 문자

이모지(😀), 힣 · 똠 같은 KS X 1001 밖의 한글, 일부 특수문자는 EUC-KR로 인코딩할 수 없어 거절됩니다. 400 응답에 어떤 문자가 문제인지 함께 옵니다.

{ "error": "EUC-KR로 보낼 수 없는 문자가 있습니다: 😀 (이모지, 일부 특수문자·한글은 지원하지 않습니다)" }

사용자 입력을 그대로 넣는 경우라면 보내기 전에 걸러내거나, 400 을 받아 사용자에게 되돌려 주세요.

08호출 한도

대상기본값
API 키 1개당 분당60건
API 키 1개당 하루2,000건

초과하면 429 와 함께 Retry-After(재시도까지 남은 초) · RateLimit-Limit · RateLimit-Remaining · RateLimit-Reset 헤더가 옵니다. Retry-After 값만큼 기다렸다 재시도하면 됩니다.

주의

검증 실패(400)도 한도를 소비합니다. 한도 검사가 본문 검증보다 먼저 돌기 때문이며, 잘못된 요청을 무한히 던지는 것을 막기 위한 의도된 동작입니다. 형식 검증은 호출하는 쪽에서 먼저 하세요.

한도 카운터는 서버 프로세스 메모리에 있습니다. 서버가 재배포·재시작되면 초기화됩니다.

09예제

curl

curl -X POST https://sms.koax.site/api/v1/messages \
  -H "Authorization: Bearer $SMS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"to":"01012345678","content":"[안내] 접수되었습니다.","refId":"order-9001"}'

Node.js

async function sendSms({ to, content, refId }) {
  const res = await fetch('https://sms.koax.site/api/v1/messages', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.SMS_API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ to, content, refId }),
  });

  const body = await res.json();

  if (res.status === 201) return body.message;

  if (res.status === 429) {
    const after = Number(res.headers.get('Retry-After') || 60);
    throw new Error(`한도 초과. ${after}초 뒤 재시도`);
  }

  // 발송 여부 불명 — 재시도하면 중복 발송될 수 있다. 호출부가 판단하도록 구분해 던진다.
  if (res.status === 502 && body.message?.status === 'unknown') {
    const err = new Error(`발송 여부 불명 (이력 #${body.message.id})`);
    err.indeterminate = true;
    err.messageId = body.message.id;
    throw err;
  }

  throw new Error(body.error || `발송 실패 (HTTP ${res.status})`);
}

PHP

function send_sms(string $to, string $content, ?string $refId = null): array {
    $ch = curl_init('https://sms.koax.site/api/v1/messages');
    curl_setopt_array($ch, [
        CURLOPT_POST           => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER     => [
            'Authorization: Bearer ' . getenv('SMS_API_KEY'),
            'Content-Type: application/json',
        ],
        CURLOPT_POSTFIELDS => json_encode(
            ['to' => $to, 'content' => $content, 'refId' => $refId],
            JSON_UNESCAPED_UNICODE
        ),
        CURLOPT_TIMEOUT => 40,   // 브리지 타임아웃(30초)보다 길게 잡는다
    ]);

    $raw    = curl_exec($ch);
    $status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    $body = json_decode($raw, true);

    if ($status === 201) {
        return $body['message'];
    }
    if ($status === 502 && ($body['message']['status'] ?? '') === 'unknown') {
        // 이미 발송됐을 수 있다. 그냥 재시도하지 말 것.
        throw new RuntimeException('발송 여부 불명: 이력 #' . $body['message']['id']);
    }
    throw new RuntimeException($body['error'] ?? "발송 실패 (HTTP {$status})");
}

본문은 UTF-8로 보내세요. EUC-KR 변환은 서버가 합니다.

10연동 전 확인할 것

11제공하지 않는 것

발송 결과 콜백
웹훅이 없습니다. 상태가 궁금하면 GET /api/v1/messages/{id} 로 조회하세요.
예약발송
호출 즉시 발송됩니다.
다건 발송
건별로 호출하고 분당 60건 한도를 지키세요.
LMS · MMS
단문 전용이며 80바이트를 넘길 수 없습니다.