
[Web 발신] 어쩌구 저쩌구 광고...
이런 웹 발신 광고 메시지 같은 걸 전송할 수 있도록 하는 문자 서비스를 제공하는 사이트들이 존재한다. 이번 포스팅에서는 문자 서비스를 연동하는 과정과 그 과정에서 발생했던 문제들에 대한 트러블 슈팅 내용을 작성할 예정이다.
알리고 문자 API
이번에 웹 발신 문자를 전송하기 위해서 이용한 서비스는 알리고 문자 서비스였다. 문자 서비스를 연동하는 과정도 처음이었기에 꽤나 겁을 먹고 시작했던 것과 다르게 친절한 API 명세를 확인할 수 있었다.
문자를 전송하기 위해서 등록해야 하는 정보는 크게 4가지로 나눌 수 있다.
1. 문자 API 담당자
2. API Key
3. 발송 서버 IP
4. 발신 번호
말 그대로 문자 API를 연결하고 관리를 담당하는 담당자를 의미한다. 문자 서비스를 이용하기 위해서는 이용하는 서비스의 사이트에 선불로 금액을 충전해야 한다. 충전한 금액은 포인트로 전환되고 문자를 전송할 때마다 사용한 만큼 포인트가 감소하는 형식이다. 따라서 문자를 전송하고 싶더라도 충전한 포인트가 바닥난다면 문자를 전송할 수 없는 상황이 발생할 수 있다. 이런 상황을 방지하기 위해서 문자 API 담당자에게 잔여 포인트가 10,000p 이하일 경우 알림을 전송하는 기능을 제공해준다.
문자 서비스를 이용하기 위해서 포인트를 충전해둔 경우 API 호출이 실제 사용자가 맞는지 확인하기 위해서 사용하는 API Key이다. API Key를 통해서 API의 사용자 식별이 이루어지기 때문에 당연히 비밀로 관리해야 한다.🤫
문자 발송 요청을 전송할 서버의 IP를 등록해야 한다.
발송 서버의 IP 주소를 등록해야 한다는 점이 다른 API를 연동할 때와의 차이점이었던 것 같다. 이전에 Chat GPT나 Toss API를 연동할 때에도 API 호출 서버의 IP를 등록한 적은 없었다. 하지만 이렇게 발송 서버 IP를 등록한 경우 실제 요청을 보낼 수 있는 서버의 IP들을 화이트 리스트로 관리할 수 있기 때문에 API Key가 탈취당하더라도 보안을 유지할 수 있다는 장점이 있다.
수신자가 문자를 수신했을 때 문자를 발신한 사람으로 보이는 번호를 등록하는 것을 말한다.

알리고 문자 서비스 사이트를 보면 위와 같이 상세한 API 스펙을 확인할 수 있다. 인증용 API Key, 사용자 ID, 발신자 전화번호, 수신자 전화번호, 메시지 내용 이렇게 5가지 필드가 필수로 들어가야 하는 정보다. 그리고 메시지 API 연동 과정에서 가장 유용하게 사용했던 부분은 바로 testmode_yn 필드이다. testmode_yn 필드에 Y, N을 String 타입으로 입력하면 테스트와 실제 메시지 전송을 선택해서 API를 호출할 수 있었다. 서버에서 자체적으로 테스트를 하는 과정에서도 유용하게 사용했을 뿐 아니라 클라이언트 쪽에서 API 연결 과정에서 호출할 때에도 테스트를 지정해서 호출할 수 있었기에 API 연결이 수월할 수 있었다.
나는 바보였다...

문자 API 명세를 보면 위와 같은 문구가 적힌 것을 확인할 수 있다. 이 문구가 의미하는 것은 통신사에서 실제로 문자를 전송하는 과정에서 EUC-KR 인코딩 방식을 사용한다는 말이다... 하지만 나는 이걸 API 호출 과정에서 EUC-KR 방식으로 인코딩해서 필드에 값을 담아야 한다고 이해했기에 문제가 시작됐다...
한 명의 수신자에게 문자를 전송할 때는 분명 정상적으로 API를 호출했지만 2명 이상부터는 자꾸 수신자를 입력해야 한다는 에러 메시지를 응답으로 받을 수 있었다.
"message": "받는이가 설정되지 못하였습니다.",
"errorCode": "MESSAGE_SEND_ERROR"
콘솔에서 로그를 찍어봤을 때 분명 2명 이상의 전화번호를 ,로 잘 구분해서 전송하고 있는데 도대체 뭐가 문제인가 싶었기에 이틀 정도 머리를 싸매고 있었던 것 같다. 도저히 내 힘으로는 해결할 수 없을 것 같았기에 알리고 문자 서비스 QnA 게시판에 글을 남겼다. 그 결과 받은 답변을 보고 이 문제를 깨달을 수 있었다.

,로 잘 구분해서 수신자를 값에 담아서 요청하고 있었다고 생각했는데 EUC-KR로 인코딩해서 전송하는 과정에서 ,가 %2C+로 인코딩 되고 있었던 것이 문제였다. 이걸 캐치하고 나서는 위에서 말했던 것처럼 EUC-KR 인코딩을 내가 할 필요가 없다는 사실을 깨달을 수 있었고, 문제를 해결할 수 있었다!!
,가 EUC-KR 인코딩 되었을 때 %2C+로 바뀐다는 사실을 알 수 있었던 유익한(?) 시간이었다...
두 번째로 고생했던 부분은 메시지 API를 호출하고 나서 응답을 파싱하는 과정이었다. (응답 바디는 JSON 객체로 구성된다고 했잖아요...)

메시지 API를 호출하고 나서 성공, 실패 등의 정보를 위와 같은 형식으로 받을 수 있을 때 응답을 매핑하기 위해서 다음과 같이 DTO를 작성했다.
@Getter
@AllArgsConstructor
public class MessageResponse {
private String resultCode;
private String message;
}
그런데 아무리 해도 파싱이 되지 않는 문제가 발생했다.
ObjectMapper objectMapper = new ObjectMapper();
MessageResponse response = objectMapper.readValue(rawResponse, MessageResponse.class);
이렇게 ObjectMapper를 사용해서 응답을 DTO로 파싱하려고 했으나 자꾸 실패하기에 응답에 문제가 있는 건가? 라는 생각이 들어서 응답 메시지를 콘솔에서 로그로 확인해봤다.
{"result_code":"1","message":"success","msg_id":"1009116229","success_cnt":1,"error_cnt":0,"msg_type":"SMS"}
응답은 문제 없이 제대로 오고 있는 걸 확인할 수 있었다.
그럼 문제가 무엇인지 고민하고 있을 때 응답의 컨텐츠 타입이 다른 것을 확인할 수 있었다. 내가 기대하는 응답의 컨텐츠 타입은 application/json 이었지만 실제로 받는 응답의 컨텐츠 타입은 Response Content-Type: text/html;charset=UTF-8 였다.
text/html 타입으로 응답을 수신하기 때문에 바로 json 파싱을 하는 과정에서 문제가 발생했던 것이다.
ObjectMapper objectMapper = new ObjectMapper();
JsonNode jsonNode = objectMapper.readTree(rawResponse);
String resultCode = jsonNode.get("result_code").asText();
String message = jsonNode.get("message").asText();
이렇게 json 파싱을 하나하나 가져오는 것으로 응답을 처리할 수 있었다. 메시지 API 호출 응답이 생각이랑 달라서 고생고생하면서 해낼 수 있었다. 하지만 API 명세에서 응답 바디는 JSON으로 제공된다고 했던 것이 완전 거짓말이라고 하기도 뭐한... 그런 상황이었다...
사진 출처
알리고 문자 서비스