웹 서비스 개발 중 사용자 인증 번호 발송이나 결제 알림을 위해 네이버 클라우드 플랫폼의 SENS(Simple & Easy Notification Service) API를 연동하려다 인증 오류(401 Unauthorized)로 몇 시간씩 애를 먹어본 경험이 있으신가요? 문서를 보며 헤더를 맞추어 전송해도 'Signature is invalid' 에러가 발생하면 당황하기 십상입니다. 다만 대부분의 개발자들은 SENS API가 요구하는 HMAC-SHA256 서명 방식의 문자열 조합 규칙과 타임스탬프 처리 방식을 정확히 이해하지 못한 채 인터넷 예제를 복사해 붙여넣다 실패를 겪곤 합니다. 이번에는 네이버 클라우드 SENS API의 서명 인증 원리부터 PHP로 실시간 SMS를 안전하고 정확하게 발송하는 방법까지 완벽하게 정리해서 소개하겠습니다.

 

1단계: Naver Cloud SENS API 및 HMAC 서명 이해하기

네이버 클라우드 SENS(Simple & Easy Notification Service)는 SMS, LMS, MMS 및 카카오톡 알림톡 메시지를 API 호출만으로 대량 또는 실시간 발송할 수 있는 클라우드 서비스입니다.
일반적인 API가 단일 Bearer 토큰이나 API Key만을 헤더에 실어 보내는 것과 달리, 네이버 클라우드 API는 보안성 강화를 위해 **요청 시점마다 가변하는 타임스탬프와 HMAC SHA256 서명(Signature)**을 필수 인증 헤더로 요구합니다.

인증 헤더 항목설명예시 / 형식
x-ncp-apigw-timestamp요청 시점의 Epoch 밀리초(milliseconds) 타임스탬프1700000000000
x-ncp-iam-access-key네이버 클라우드 마이페이지에서 발급받은 Access Key IDncp_iam_AK...
x-ncp-apigw-signature-v2HTTP 메소드, URL 경로, 타임스탬프, Access Key를 Secret Key로 암호화한 HMAC 서명Base64 인코딩된 문자열

 

2단계: API 연동 준비 및 헤더 조합 규칙

SENS API로 메시지를 전송하기 전, 네이버 클라우드 콘솔에서 Service ID를 확인하고 API Access Key/Secret Key 쌍을 발급받아야 합니다.
서명(Signature)을 생성하기 위한 텍스트 조합 규칙은 다음과 같이 매우 엄격합니다.

서명 생성 시 결합되는 raw string 규칙:
1. HTTP Method (예: POST)
2. 한 칸 공백 (Space)
3. URI 경로 (도메인을 제외한 /sms/v2/services/{serviceId}/messages 전체)
4. 줄바꿈 (\n)
5. Timestamp (밀리초 단위)
6. 줄바꿈 (\n)
7. Access Key ID

 

3단계: PHP 실전 발송 및 서명 생성 예제

PHP cURL을 활용하여 HMAC SHA256 서명을 생성하고 SMS 메시지를 전송하는 실전 코드입니다.

✗ 잘못된 코드 (단순 초 단위 타임스탬프 사용 및 필수 헤더 조합 누락):

// ✗ 서명 문자열 포맷 누락 및 타임스탬프 단위 오류
$timestamp = time(); // 초 단위 time()을 그대로 사용하면 401 인증 실패 발생
$rawString = "POST /sms/v2/services/".$serviceId."/messages"; // 줄바꿈과 AccessKey 결합 누락
$signature = base64_encode(hash_hmac('sha256', $rawString, $secretKey, true));

✓ 올바른 코드 (밀리초 타임스탬프 계산 및 정확한 서명 조합 후 cURL 전송):

<?php
function sendNaverSms($accessKey, $secretKey, $serviceId, $fromPhone, $toPhone, $content) {
    // 1. Epoch 밀리초 타임스탬프 생성
    $timestamp = (string)round(microtime(true) * 1000);
    
    // 2. 요청 URI 정의
    $uri = "/sms/v2/services/{$serviceId}/messages";
    $url = "https://sens.apigw.ntruss.com" . $uri;

    // 3. HMAC-SHA256 암호화 대상 문자열 조합 (HTTP Method + 공백 + URI + \n + timestamp + \n + accessKey)
    $message = "POST" . " " . $uri . "\n" . $timestamp . "\n" . $accessKey;

    // 4. Secret Key를 이용한 HMAC SHA256 서명 생성 및 Base64 인코딩
    $signature = base64_encode(hash_hmac('sha256', $message, $secretKey, true));

    // 5. 요청 데이터 구조 생성
    $data = [
        'type' => 'SMS', // SMS 또는 LMS
        'contentType' => 'COMM', // COMM(일반) 또는 AD(광고)
        'countryCode' => '82',
        'from' => $fromPhone, // 발신번호 (네이버 클라우드에 사전 등록된 번호)
        'content' => $content,
        'messages' => [
            ['to' => $toPhone]
        ]
    ];

    $headers = [
        'Content-Type: application/json; charset=utf-8',
        'x-ncp-apigw-timestamp: ' . $timestamp,
        'x-ncp-iam-access-key: ' . $accessKey,
        'x-ncp-apigw-signature-v2: ' . $signature
    ];

    // 6. cURL 요청 수행
    $ch = curl_init();
    curl_setopt($ch, CURLOPT_URL, $url);
    curl_setopt($ch, CURLOPT_POST, true);
    curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
    curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    curl_setopt($ch, CURLOPT_TIMEOUT, 10);

    $response = curl_exec($ch);
    $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);

    return [
        'code' => $httpCode,
        'body' => json_decode($response, true)
    ];
}

// 실무 적용 예시
$accessKey = "YOUR_NCP_ACCESS_KEY";
$secretKey = "YOUR_NCP_SECRET_KEY";
$serviceId = "ncp:sms:kr:1234567890:service_name";

$result = sendNaverSms($accessKey, $secretKey, $serviceId, "01012345678", "01098765432", "[서비스명] 인증번호는 5892 입니다.");
print_r($result);
?>

출력 결과 (API 호출 성공 시):

{
    "code": 202,
    "body": {
        "requestId": "20231025-102938-1234567",
        "requestTime": "2023-10-25T10:29:38.123",
        "statusCode": "202",
        "statusName": "success"
    }
}

 

4단계: 실무 주의사항 및 흔한 실수 해결법

SENS API 연동 중 가장 자주 발생하는 문제들과 해결책입니다.

  • **HTTP 401 Unauthorized (Signature is invalid)**: 서명 생성 시 암호화 문자열 사이에 줄바꿈(`\n`) 대신 공백을 넣었거나 URI 경로의 대소문자가 틀린 경우입니다. 조합 규칙을 다시 한번 확인하세요.
  • **HTTP 400 Bad Request (Invalid Sender Number)**: `from`에 입력한 발신번호가 네이버 클라우드 콘솔의 [발신번호 관리] 메뉴에 등록되어 있지 않거나 통신사 본인인증이 완료되지 않은 경우 발생합니다.
  • **서버 타임스탬프 시간 오차**: API를 호출하는 웹 서버의 시계가 네이버 타임 서버와 5분 이상 벌어지면 인증이 거부됩니다. Linux 서버의 NTP(Network Time Protocol) 동기화 상태를 체크하세요.

 

5단계: 요약 및 마무리

네이버 클라우드 SENS API 연동은 HMAC SHA256 서명 생성 규칙만 정확히 지키면 매우 안정적이고 빠르게 메시지를 발송할 수 있는 강력한 도구입니다. 복잡해 보이는 보안 인증 절차도 밀리초 단위 타임스탬프 처리와 정해진 서명 포맷을 서포트 함수로 모듈화해 두면 재사용성과 보안을 모두 확보할 수 있습니다.

Naver Cloud SENS API는 회원가입 인증, 결제 완료 통지, 비상 상태 알림 등 웹 서비스의 핵심 소통 채널을 담당하는 중요한 기능이다. 작은 서명 생성 최적화와 예외 처리 습관이 모여서 서비스 전체의 안정성과 신뢰도를 만든다는 점을 잊지 말자. 이 글의 PHP 서포트 함수 예제를 참고해 본인의 프로젝트에 바로 연동해 보면, 인증 실패 없이 완벽하게 동작하는 SMS 알림 시스템을 구현할 수 있을 것이다.