이번 편에서는 1편에서 세팅한 내용들을 바탕으로 스프링 부트 프로젝트에서 알림톡을 발송 코드 작성을 다룹니다.
NCP 알림톡 API 가이드
https://api.ncloud-docs.com/docs/ai-application-service-sens-alimtalkv2
공식 docs 링크는 위와 같으며 위 문서를 참고하여 작성했습니다!
1. 메시지 발송 API 기본 정보
얼핏보면 내용이 많아 복잡해보일 수 있지만 해당 요청 주소로 필수로 요구하는 정보들을 채워 보내면 됩니다!
아래는 필요한 정보들을 정리한 것으로 마지막에 코드 예시로도 보여드리겠습니다.
요청 URL
POST https://sens.apigw.ntruss.com/alimtalk/v2/services/{serviceId}/messages
필수 path variables
serviceId String
필수 API header
Content-Type | 요청 Body Content Type을 application/json으로 지정 (POST) |
x-ncp-apigw-timestamp | - 1970년 1월 1일 00:00:00 협정 세계시(UTC)부터의 경과 시간을 밀리초(Millisecond)로 나타냄 - API Gateway 서버와 시간 차가 5분 이상 나는 경우 유효하지 않은 요청으로 간주 |
x-ncp-iam-access-key | 포탈 또는 Sub Account에서 발급받은 Access Key ID |
x-ncp-apigw-signature-v2 | - 위 예제의 Body를 Access Key Id와 맵핑되는 SecretKey로 암호화한 서명 - HMAC 암호화 알고리즘은 HmacSHA256 사용 |
요청 body (필수+옵션)
{
"plusFriendId":"string",
"templateCode":"string",
"messages":[
{
"countryCode":"string",
"to":"string",
"title":"string",
"content":"string",
"headerContent":"string",
"itemHighlight":{
"title":"string",
"description":"string"
},
"item":{
"list":[
{
"title":"string",
"description":"string"
}
],
"summary":{
"title":"string",
"description":"string"
}
},
"buttons":[
{
"type":"string",
"name":"string",
"linkMobile":"string",
"linkPc":"string",
"schemeIos":"string",
"schemeAndroid":"string"
}
],
"useSmsFailover": "boolean",
"failoverConfig": {
"type": "string",
"from": "string",
"subject": "string",
"content": "string"
}
}
],
"reserveTime": "yyyy-MM-dd HH:mm",
"reserveTimeZone": "string",
"scheduleCode": "string"
}
plusFriendId | Mandatory | String | 카카오톡 채널명 ((구)플러스친구 아이디) |
|
templateCode | Mandatory | String | 템플릿 코드 | |
messages | Mandatory | Object | 메시지 정보 | - 아래 항목 참조 (messages.XXX) - 최대 100개 |
messages.countryCode | Optional | String | 수신자 국가번호 | |
messages.to | Mandatory | String | 수신자번호 | |
messages.title | Optional | String | 알림톡 강조표시 내용 | 강조 표기 유형의 템플릿에서만 사용 가능 |
messages.content | Mandatory | String | 알림톡 메시지 내용 | |
messages.headerContent | Optional | String | 알림톡 헤더 내용 | - 아이템 리스트 유형의 템플릿에서만 사용 가능 - 최대 16자까지 입력 가능 |
messages.itemHighlight | Optional | Object | 아이템 하이라이트 | 아이템 리스트 유형의 템플릿에서만 사용 가능 |
messages.itemHighlight.title | Mandatory | String | 아이템 하이라이트 제목 | - 아이템 리스트 유형의 템플릿에서만 사용 가능 이미지가 없는 경우 - 최대 30자까지 입력 가능 (2줄) - 1줄은 15자까지 입력 가능 이미지가 있는 경우 - 최대 21자까지 입력 가능 (2줄) - 1줄은 10자까지 입력 가능 - 2줄 초과 시 말줄임 처리 |
messages.itemHighlight.description | Mandatory | String | 아이템 하이라이트 설명 | - 아이템 리스트 유형의 템플릿에서만 사용 가능 이미지가 없는 경우 - 최대 19자까지 입력 가능 (1줄) 이미지가 있는 경우 - 최대 13자까지 입력 가능 (1줄) - 1줄 초과 시 말줄임 처리 |
messages.item | Optional | Object | 아이템 리스트 | 아이템리스트 유형의 템플릿에서만 사용 가능 |
messages.messages.item.list | Mandatory | Array of Object | 아이템 리스트 | - 아이템리스트 유형의 템플릿에서만 사용 가능 - 최소 2개 이상, 최대 10개 |
messages.messages.item.list.title | Mandatory | String | 아이템 리스트 제목 | - 아이템리스트 유형의 템플릿에서만 사용 가능 - 최대 6자까지 입력 가능 |
messages.messages.item.list.description | Mandatory | String | 아이템 리스트 설명 | - 아이템리스트 유형의 템플릿에서만 사용 가능 - 최대 23자까지 입력 가능 |
messages.messages.summary | Optional | Object | 아이템 요약 정보 | 아이템리스트 유형의 템플릿에서만 사용 가능 |
messages.messages.summary.title | Mandatory | String | 아이템 요약 제목 | - 아이템리스트 유형의 템플릿에서만 사용 가능 - 최대 6자까지 입력 가능 |
messages.messages.summary.description | Mandatory | String | 아이템 요약 설명 | - 아이템리스트 유형의 템플릿에서만 사용 가능 - 허용되는 문자: 통화기호(유니코드 통화기호, 元, 円, 원), 통화코드 (ISO 4217), 숫자, 콤마, 소수점, 공백 - 소수점 2자리까지 허용 - 최대 23자까지 입력 가능 |
messages.buttons | Optional | Array of Object | 알림톡 메시지 버튼 | 아래 템플릿 버튼 정보 참조 |
messages.buttons.type | Mandatory | String | 버튼 Type | 아래 템플릿 버튼 정보 참조 |
messages.buttons.name | Mandatory | String | 버튼명 | 아래 템플릿 버튼 정보 참조 |
messages.useSmsFailover | Optional | Boolean | SMS Failover 사용 여부 | - Failover가 설정된 카카오톡 채널에서만 사용 가능 - 기본: 카카오톡 채널의 Failover 설정 여부를 따름 |
messages.failoverConfig | Optional | Object | Failover 설정 | 아래 항목 참조 |
messages.failoverConfig.type | Optional | String | Failover SMS 메시지 Type | - SMS 또는 LMS - 기본: content 길이에 따라 자동 적용 (90 bytes 이하 SMS, 초과 LMS) |
messages.failoverConfig.from | Optional | String | Failover SMS 발신번호 | - 기본: Failover 설정 시 선택한 발신번호 - 승인되지 않은 발신번호 사용시 Failover 동작 안함 |
messages.failoverConfig.subject | Optional | String | Failover SMS 제목 | - LMS type으로 동작할 때 사용 - 기본: 카카오톡 채널명 |
messages.failoverConfig.content | Optional | String | Failover SMS 내용 | 기본: 알림톡 메시지 내용 (버튼 제외) |
reserveTime | Optional | String | 예약 일시 | 메시지 발송 예약 일시 (yyyy-MM-dd HH:mm) |
reserveTimeZone | Optional | String | 예약 일시 타임존 | - 예약 일시 타임존 (기본: Asia/Seoul) - 지원 타임존 목록 * TZ database name 값 사용 |
scheduleCode | Optional | String | 스케줄 코드 | 등록하려는 스케줄 코드 |
응답 status
202 | Accepted (발송 요청 완료) |
400 | Bad Request |
401 | Unauthorized |
403 | Forbidden |
404 | Not Found |
500 | Internal Server Error |
응답 body
{
"requestId":"string",
"requestTime":"string",
"statusCode":"string",
"statusName":"string",
"messages":[
{
"messageId":"string",
"countryCode":"string",
"to":"string",
"content":"string",
"requestStatusCode":"string",
"requestStatusName":"string",
"requestStatusDesc":"string",
"useSmsFailover":"boolean"
}
]
}
requestId | Mandatory | String | 발송 요청 아이디 | |
requestTime | Mandatory | DateTime | 발송 요청 시간 | |
statusCode | Mandatory | String | 요청 상태 코드 | - 성공: 202 - 실패: 그 외 - HTTP Status 규격을 따름 |
statusName | Mandatory | String | 요청 상태명 | - 성공: success - 처리 중: processing - 예약 중: reserved - 스케줄 중: scheduled - 실패: fail |
messages.messageId | Mandatory | String | 메시지 아이디 | |
messages.countryCode | Optional | String | 수신자 국가번호 | |
messages.to | Mandatory | String | 수신자 번호 | |
messages.content | Mandatory | String | 알림톡 메시지 내용 | |
messages.requestStatusCode | Mandatory | String | 발송요청 상태 코드 | - 성공: A000 - 실패: 그 외 코드(Desc 항목에 실패 사유가 명시) |
messages.requestStatusName | Mandatory | String | 발송 요청 상태명 | - 성공: success - 실패: fail |
messages.requestStatusDesc | Mandatory | String | 발송 요청 상태 내용 |
|
messages.useSmsFailover | Mandatory | Boolean | SMS Failover 사용 여부 |
2. 메시지 발송 코드 작성 예시
크게 4가지 단계로 나눠볼 수 있습니다.
(1) 서명 생성
(2) 헤더 생성
(3) 바디 생성
(4) api 요청
전체 코드 예시
변수들 설명 및 유의 사항
plusFriendId: 카톡플러스친구채널 id. @를 붙여서 넣어주세요.
ex) @chaech2929
to: 알림톡을 보낼 전화번호 String 타입
templateCode: 알림톡 템플릿 등록 시 설정했던 식별 코드
content: 알림톡 템플릿 등록 시 설정했던 글 내용과 동일하게 넣어주세요. 변수 값은 코드에서 사용하던 변수에 맞춰 알맞은 걸로 채워서 넣어주시면 됩니다.
ex) String content = userName + "님, 두둥에 가입하신 것을 환영합니다!\n"
signature 생성 시 사용하는 timestamp 값과 api 요청시 헤더에 넣는 timestamp 값이 동일해야합니다.
가장 기본 옵션으로만 이루어진 메시지를 기준으로 작성했습니다.
package band.gosrock.infrastructure.config.AlilmTalk;
import java.io.IOException;
import java.sql.Timestamp;
import java.time.Instant;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.configurationprocessor.json.JSONArray;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
@Component
@Configuration
public class AlimTalk {
private final String serviceID;
private final String ncpAccessKey;
private final String ncpSecretKey;
private final String plusFriendId;
public AlimTalk(
@Value("${ncp.service-id}") String serviceID,
@Value("${ncp.access-key}") String ncpAccessKey,
@Value("${ncp.secret-key}") String ncpSecretKey,
@Value("${ncp.plus-friend-id}") String plusFriendId) {
this.serviceID = serviceID;
this.ncpAccessKey = ncpAccessKey;
this.ncpSecretKey = ncpSecretKey;
this.plusFriendId = plusFriendId;
}
public void sendAlimTalk(String to, String templateCode, String content) {
String alimTalkSendRequestUrl =
"https://sens.apigw.ntruss.com/alimtalk/v2/services/" + serviceID + "/messages";
String alimTalkSignatureRequestUrl = "/alimtalk/v2/services/" + serviceID + "/messages";
CloseableHttpClient httpClient = null;
try {
// signature 생성
String[] signatureArray =
makePostSignature(ncpAccessKey, ncpSecretKey, alimTalkSignatureRequestUrl);
// http 통신 객체 생성
httpClient = HttpClients.createDefault(); // http client 생성
HttpPost httpPost = new HttpPost(alimTalkSendRequestUrl); // post 메서드와 URL 설정
// 헤더 설정
httpPost.setHeader("Content-Type", "application/json; charset=UTF-8");
httpPost.setHeader("x-ncp-iam-access-key", ncpAccessKey);
httpPost.setHeader("x-ncp-apigw-timestamp", signatureArray[0]);
httpPost.setHeader("x-ncp-apigw-signature-v2", signatureArray[1]);
// body 설정
JSONObject msgObj = new JSONObject();
msgObj.put("plusFriendId", plusFriendId);
msgObj.put("templateCode", templateCode);
JSONObject messages = new JSONObject();
messages.put("to", to);
messages.put("content", content);
JSONArray messageArray = new JSONArray();
messageArray.put(messages);
msgObj.put("messages", messageArray);
// api 전송 값 http 객체에 담기
httpPost.setEntity(new StringEntity(msgObj.toString(), "UTF-8"));
// api 호출
CloseableHttpResponse httpResponse = httpClient.execute(httpPost);
// 응답 결과
String result = EntityUtils.toString(httpResponse.getEntity(), "UTF-8");
System.out.println(result);
} catch (Exception ex) {
// TODO: 에러 처리
ex.printStackTrace();
} finally {
try {
httpClient.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public String[] makePostSignature(String accessKey, String secretKey, String url) {
String[] result = new String[2];
try {
String timeStamp = String.valueOf(Instant.now().toEpochMilli()); // current timestamp (epoch)
String space = " "; // space
String newLine = "\n"; // new line
String method = "POST"; // method
String message =
new StringBuilder()
.append(method)
.append(space)
.append(url)
.append(newLine)
.append(timeStamp)
.append(newLine)
.append(accessKey)
.toString();
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
String encodeBase64String = Base64.encodeBase64String(rawHmac);
result[0] = timeStamp;
result[1] = encodeBase64String;
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
public String[] makeGetSignature(String accessKey, String secretKey, String url) {
String[] result = new String[2];
try {
String timeStamp = String.valueOf(Instant.now().toEpochMilli()); // current timestamp (epoch)
String space = " "; // space
String newLine = "\n"; // new line
String method = "GET"; // method
String message =
new StringBuilder()
.append(method)
.append(space)
.append(url)
.append(newLine)
.append(timeStamp)
.append(newLine)
.append(accessKey)
.toString();
SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes("UTF-8"), "HmacSHA256");
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(signingKey);
byte[] rawHmac = mac.doFinal(message.getBytes("UTF-8"));
String encodeBase64String = Base64.encodeBase64String(rawHmac);
result[0] = timeStamp;
result[1] = encodeBase64String;
} catch (Exception ex) {
ex.printStackTrace();
}
return result;
}
}
참고 사이트
https://api.ncloud-docs.com/docs/ai-application-service-sens-alimtalkv2
https://honeystorage.tistory.com/191