Telegram 알림 분리: 수신자 인증과 전송 상태까지 설계하기
TECH INSIGHT / EXPLAINED
사용자에게 필요한 것은 ‘주문이 어떻게 됐는가’이고 운영자에게 필요한 것은 ‘왜 그런 결과가 나왔는가’다. 하나의 사건에서 두 종류의 알림을 만들면 정보가 묻히는 일을 줄일 수 있다. 다만 채널을 나눴다는 사실만으로 개인정보 분리가 완성되지는 않는다.
실제 프로젝트의 로컬 실행 화면
기존 프로젝트의 화면 또는 함수를 로컬에서 실행해 촬영했습니다. 개인정보 없는 샘플 입력을 사용했으며, 실행한 범위는 각 이미지 아래에 설명했습니다.
같은 사건을 두 수준으로 설명한다
주문 거절을 예로 들면 사용자 메시지는 ‘매수 요청이 거절됐습니다. 주문 내역을 확인하세요’와 사건 시각을 담는다. 운영자 메시지는 같은 event_id, 오류 분류, 재시도 횟수, 마스킹된 계정 식별자를 담는다. 원시 API 응답과 예외 전체를 채팅에 복사하지 않는다. 토큰과 계좌 정보가 섞일 수 있다.
주문 접수와 체결도 구분한다. 증권사 접수 응답만 받았는데 ‘매수 완료’라고 알리면 사용자가 실제 보유량을 오해한다. 이벤트 이름을 접수·부분 체결·체결·취소·결과 확인 필요처럼 명시한다.
채팅 연결은 인증 절차다
사용자 ID를 포함한 메시지를 찾아 연결하는 text.includes(userid) 방식은 타인이 ID를 적어 보내도 매칭될 수 있다. 로그인한 서비스 화면에서 충분히 예측 불가능한 일회용 연결 토큰을 발급하고, 짧은 만료 시간과 발급 사용자, 사용 여부를 서버에 저장한다.
- 사용자가 토큰을 포함한 봇의 start 링크를 직접 연다.
- 서버는 개인 대화에서 받은 토큰을 정확히 비교하고 만료·재사용·소유자를 확인한다.
- 한 번만 소비되도록 저장을 원자적으로 처리하고 서비스 사용자와 Telegram 사용자·chat_id를 연결한다.
- 연결 결과를 원래 로그인 화면에서도 확인하게 하고 해제·재연결 경로를 제공한다.
기기를 바꾼다는 이유만으로 동일한 개인 대화의 chat_id가 바뀌는 것은 아니다. 개인 대화 수신에는 사용자가 봇과 먼저 대화를 시작하는 과정이 필요하다.
업데이트를 한 곳에서 소비한다
getUpdates는 기본적으로 가장 이른 미확인 업데이트부터 반환한다. limit=10을 ‘최신 10개’로 해석하지 않는다. 한 소비자가 처리한 update_id 다음 값을 offset으로 저장하고 중복 처리에 대비한다. 웹훅이 설정된 동안에는 getUpdates를 사용할 수 없으므로 빈 배열을 정상 동작으로 가정해서는 안 된다.
Apps Script의 User Properties는 실행 사용자 기준이다. 설치형 트리거가 생성자 계정으로 실행되는 상황에서는 웹 앱 사용자의 저장 범위가 그대로 따라오지 않는다. 작업에 인증된 서비스 사용자 ID를 연결하고 그 사용자의 수신처를 명시적으로 조회한다.
전송 실패는 별도의 상태로 관리한다
봇 토큰을 둘로 나누면 회전과 접근 권한을 독립적으로 관리하기 쉽다. 한 사용자가 봇을 차단해도 다른 채팅 전체가 함께 차단되는 것은 아니다. 어느 설계든 수신처 권한, 채널 멤버십과 데이터 마스킹을 확인해야 한다.
전송 작업은 event_id, 수신처, 시도 횟수, 다음 시도 시각을 가진 큐로 관리한다. HTTP 오류뿐 아니라 API의 ok 값과 retry_after를 확인한다. 50ms 대기만으로 채팅별·봇별 제한을 모두 지킬 수는 없다. 같은 사건의 중복 알림과 반복 오류 폭주를 억제하되, 제한 횟수 이후 실패는 화면에 남긴다.
알림 실패 때문에 이미 처리된 주문을 다시 보내면 안 된다. 반대로 감시 알림의 장기 장애에도 무조건 새 거래를 계속한다는 규칙도 적절하지 않다. 운영자가 확인할 수 없는 시간이 기준을 넘으면 신규 동작을 보류하는 정책을 정한다. ‘알림 미등록’, ‘전송 대기’, ‘전송 실패’를 구분하면 선택 기능과 장애를 혼동하지 않는다.
참고 자료
내용·예제 검토: 2026-09-29
댓글
댓글 쓰기