[Apps Script] V4 API HMAC SignatureDoesNotMatch 오류 (코드 로직 100% 검증 완료) #100
-
사용 중인 프로그래밍 언어 및 버전JavaScript (Google Apps Script / V8 Runtime) SDK 버전Solapi 공식 SDK를 사용하지 않고, Google Apps Script의 UrlFetchApp을 이용하여 HMAC-SHA256 인증을 직접 구현했습니다. 운영 환경기타 (설명란에 자세히 기재해주세요) 질문/문제 설명Google Apps Script 환경에서 V4 API를 통한 알림톡 단건 발송 기능을 구현하고 있습니다. HMAC 서명을 생성하여 요청을 보내면 계속해서 "SignatureDoesNotMatch" 오류가 발생하고 있습니다. 저희는 귀사의 공식 개발자 문서(V4 API 인증 가이드), Apps Script 튜토리얼 등을 기반으로 Apps Script 코드를 작성했으며, Apps Script에서 생성된 HMAC 서명이 귀사 API가 기대하는 것과 100% 일치함을 외부 스크립트(Python)를 통해 검증 완료했음에도 동일한 에러를 받고 있습니다. 이는 저희 코드의 HMAC 서명 로직 자체에는 오류가 없다는 강력한 증거이며, 문제의 원인이 Google Apps Script 환경에서 HTTP 요청을 보내는 과정이나 Solapi 서버의 특정 설정(시간 오차, 계정 제한 등)에 있을 것으로 판단하고 있습니다. 보안상의 이유로 API Key와 Secret은 아래 코드와 로그에 샘플 값으로 대체했습니다. 실제 테스트 시에는 유효한 키를 사용했습니다. 상세한 디버깅을 위해 필요하시다면, 안전한 비공개 채널을 통해 실제 값을 제공해 드릴 의향이 있습니다. 발생 시각 (Apps Script 서버 기준): 2025-12-04T09:53:27Z (UTC) 발생한 오류 메시지 (있는 경우): 응답 코드: 400, 응답 본문: {"errorCode":"SignatureDoesNotMatch","errorMessage":"생성한 signature를 확인하세요."} 코드 예시// Function to generate base64url encoding
function base64EncodeUrl(inputBytes) {
var base64 = Utilities.base64Encode(inputBytes);
return base64
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
// Function to generate HMAC-SHA256 signature
function generateSignature(apiKey, date, salt, method, urlPath, hashedBody, apiSecret) {
const message = [apiKey, date, salt, method, urlPath, hashedBody].join('\n');
const messageBytes = Utilities.newBlob(message, 'text/plain;charset=UTF-8', 'hmac_message').getBytes();
const apiSecretBytes = Utilities.newBlob(String(apiSecret), 'text/plain;charset=UTF-8', 'api_secret').getBytes();
const signatureBytes = Utilities.computeHmacSha256Signature(messageBytes, apiSecretBytes);
return base64EncodeUrl(signatureBytes);
}
function sendAlimtalk(recipientPhoneNumber, variables) {
// 🚨🚨🚨 보안상 샘플 값으로 변경함. 실제 테스트에는 유효한 키를 사용했음.
const apiKey = 'YOUR_ACTUAL_API_KEY_PLACEHOLDER'; // 실제 API Key는 비공개 채널 통해 제공 가능
const apiSecret = 'YOUR_ACTUAL_API_SECRET_PLACEHOLDER'; // 실제 API Secret은 비공개 채널 통해 제공 가능
// settings, getApiCredentials 등은 생략했습니다.
// 이 부분은 Apps Script 환경에 맞춰 구성되어 있습니다.
const SOLAPI_SEND_URL = 'https://api.solapi.com/messages/v4/send';
// 🚨🚨🚨 보안상 샘플 값으로 변경함. 실제 테스트에는 유효한 값을 사용했음.
const SOLAPI_PF_ID = 'YOUR_ACTUAL_PF_ID_PLACEHOLDER'; // 실제 PFID는 비공개 채널 통해 제공 가능
const ALIMTALK_TEMPLATE_ID = 'YOUR_ACTUAL_TEMPLATE_ID_PLACEHOLDER'; // 실제 템플릿 ID는 비공개 채널 통해 제공 가능
const SOLAPI_FROM_NUMBER = '01012345678'; // 유효한 형식의 샘플 발신 번호. 실제 발신 번호는 비공개 채널 통해 제공 가능
const currentDate = new Date().toISOString().split('.')[0] + 'Z';
const salt = '824efaf1-79f5-4145-9a43-7a3ebb0d154e'; // 로그에 있는 실제 salt 값.
const httpMethod = 'POST';
let urlForSignature = SOLAPI_SEND_URL;
const protocolEndIndex = urlForSignature.indexOf('://');
if (protocolEndIndex !== -1) {
urlForSignature = urlForSignature.substring(protocolEndIndex + 3);
}
const firstSlashAfterDomain = urlForSignature.indexOf('/');
if (firstSlashAfterDomain !== -1) {
urlForSignature = urlForSignature.substring(firstSlashAfterDomain);
} else {
urlForSignature = '/';
}
const queryStringIndex = urlForSignature.indexOf('?');
if (queryStringIndex !== -1) {
urlForSignature = urlForSignature.substring(0, queryStringIndex);
}
const hashIndex = urlForSignature.indexOf('#');
if (hashIndex !== -1) {
urlForSignature = urlForSignature.substring(0, hashIndex);
}
const payload = {
message: {
to: recipientPhoneNumber, // 예: '01012341234' (실제 수신 번호도 필요 시 비공개 공유)
from: SOLAPI_FROM_NUMBER,
kakaoOptions: {
pfId: SOLAPI_PF_ID,
templateId: ALIMTALK_TEMPLATE_ID,
variables: {
"#{대리점명}": "마스터대리점(테스트)",
"#{주문일시}": "2025-12-04 18:53:11",
"#{배송일}": "2025-12-05",
"#{주문상세내역}": "- 5가지 야채듬뿍 사각어묵: 1개\n- 생선살 듬뿍 사각어묵: 1개\n"
}
}
}
};
const payloadString = JSON.stringify(payload);
const hashedBodyBytes = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_256, payloadString, Utilities.Charset.UTF_8);
const hashedBody = base64EncodeUrl(hashedBodyBytes);
const signature = generateSignature(
apiKey, currentDate, salt, httpMethod, urlForSignature, hashedBody, String(apiSecret)
);
const options = {
method: httpMethod,
contentType: 'application/json',
headers: {
'Authorization': `HMAC-SHA256 apiKey=${apiKey.trim()}, date=${currentDate.trim()}, salt=${salt.trim()}, signature=${signature.trim()}`,
'Content-Type': 'application/json; charset=utf-8',
'Date': currentDate.trim()
},
payload: payloadString,
muteHttpExceptions: true
};
try {
const response = UrlFetchApp.fetch(SOLAPI_SEND_URL, options);
const responseCode = response.getResponseCode();
if (responseCode >= 200 && responseCode < 300) {
return true;
} else {
const responseBody = response.getContentText();
Logger.log(`API 발송 실패. 응답 코드: ${responseCode}, 응답 본문: ${responseBody}`);
return false;
}
} catch (e) {
Logger.log('API 호출 중 예외 발생: ' + e.toString());
return false;
}
}시도한 해결 방법
아래는 디버깅을 위해 수집된 로그 상세 내용입니다.
기대하는 결과HMAC 서명 로직이 100% 검증되었으므로, 알림톡 메시지가 성공적으로 전송되거나, 최소한 SignatureDoesNotMatch 이외의 명확한 오류 메시지(예: 잘못된 템플릿 ID, 전화번호 형식 오류 등)를 받아 문제의 원인을 파악할 수 있어야 합니다. 확인사항
|
Beta Was this translation helpful? Give feedback.
Replies: 2 comments
-
|
안녕하세요 회원님, 솔라피 기술지원팀입니다. |
Beta Was this translation helpful? Give feedback.
-
|
안녕하세요.
튜토리얼 문서와 같이 실제 코드를 변경 후, 인증 및 알림톡 송신/수신이 모두 정상 동작하는 것을 확인하였습니다.
감사합니다.
2025년 12월 5일 (금) AM 9:08, Subin Lee ***@***.***>님이 작성:
… 안녕하세요 회원님, 솔라피 기술지원팀입니다.
번거로우시겠지만 Google Apps Script 튜토리얼 문서
<https://developers.solapi.com/tutorial/2025/05/15/send-sms-with-google-apps-script>의
코드와 완전히 동일하게 복사 붙여넣기 하여 일부 값(API Key, Secret, 발신번호, 수신번호 등)만 변경하여 테스트하셨을
때에도 동일한 현상이실지 확인 부탁드리겠습니다.
—
Reply to this email directly, view it on GitHub
<#100 (comment)>,
or unsubscribe
<https://github.com/notifications/unsubscribe-auth/AXQUPYK2TK3FQPOBHOKIUKL4ADEIRAVCNFSM6AAAAACOAVGAZ2VHI2DSMVQWIX3LMV43URDJONRXK43TNFXW4Q3PNVWWK3TUHMYTKMJWGY2TCMY>
.
You are receiving this because you authored the thread.Message ID:
***@***.***>
|
Beta Was this translation helpful? Give feedback.
안녕하세요 회원님, 솔라피 기술지원팀입니다.
번거로우시겠지만 Google Apps Script 튜토리얼 문서의 코드와 완전히 동일하게 복사 붙여넣기 하여 일부 값(API Key, Secret, 발신번호, 수신번호 등)만 변경하여 테스트하셨을 때에도 동일한 현상이실지 확인 부탁드리겠습니다.