Slack 메시지를 단순 알림에서 승인·거부가 가능한 업무 UI로 확장하는 과정을 Spring과 Java Slack SDK 코드로 설명한다. Incoming Webhook으로 Block Kit 메시지를 보내고 Interactivity 요청을 API에서 받은 뒤, 원래 메시지를 결과 상태로 교체한다. 배송 팁 승인 사례를 통해 앱 설정부터 payload 역직렬화와 응답 전송까지 왕복 흐름을 보여준다.
핵심 포인트- Block Kit은 버튼·메뉴·날짜 선택기 등 Slack 메시지 안에 상호작용 가능한 UI를 구성하는 프레임워크다.
- Incoming Webhook은 메시지 전송을 담당하고 Interactivity의 Request URL은 버튼 동작을 애플리케이션 API로 전달한다.
- Java Slack SDK의 LayoutBlock 목록으로 승인·거부 버튼을 만들고 actionId로 서버 처리 분기를 구분했다.
- Slack 액션 요청은 application/x-www-form-urlencoded이며 payload 문자열 안의 JSON을 BlockActionPayload로 변환해야 했다.
- ActionResponse의 replaceOriginal을 true로 설정하고 response_url로 보내 기존 메시지를 승인·거부 결과로 교체했다.
상세 정리- 배경: 컬리는 휴가자, 개발팀 중요 이벤트, 입고 스캔 오류 같은 업무 알림을 Incoming Webhook으로 Slack에 보내고 있었다.
- 요구 변화: 배송 팁 정보를 Slack에서 바로 승인할 수 있게 해달라는 요청이 생겼다. 읽기 전용 알림이 아니라 메시지에서 이벤트를 발생시켜야 했다.
- UI 설계: Block Kit Builder에서 템플릿과 JSON을 동시에 보며 원하는 모양을 시험했다. 메시지 미리보기와 컴포넌트 구성을 전송 전에 확인할 수 있었다.
- 앱 생성: 사용할 Workspace를 선택해 Slack App을 만들고 Incoming Webhooks와 Interactivity & Shortcuts를 활성화했다.
- 전송 채널: Incoming Webhook을 원하는 채널에 연결했다. 생성된 Webhook URL이 Block 메시지 전송 목적지가 된다.
- 액션 수신: Interactivity의 Request URL에 Spring API 주소를 넣었다. action_approve와 action_reject를 식별자로 정해 버튼과 서버 분기를 연결했다.
- SDK 구성: slack-api-model, slack-api-client, slack-app-backend와 Gson, OkHttp, SLF4J 의존성을 추가했다. 글의 예시는 Slack SDK 1.0.0-RC2를 사용했다.
- 블록 생성: SectionBlock에 안내 문구를 넣고 Divider 뒤 ActionsBlock에 승인·거부 버튼을 배치했다. 각 버튼에는 대상 데이터의 seq 값과 actionId를 넣었다.
- 최초 전송: LayoutBlock 목록을 payload의 blocks에 넣어 Incoming Webhook으로 보냈다. text에는 Block 전송 실패 시 표시할 대체 문구를 지정했다.
- 요청 형식 함정: 버튼 클릭 시 Slack은 JSON 본문이 아니라 form-urlencoded의 payload 파라미터로 요청했다. RequestBody로 바로 받으면 MissingServletRequestParameterException이 발생했다.
- 역직렬화: Spring Controller가 @RequestParam String payload를 받고, Gson의 snake_case 설정으로 BlockActionPayload 객체로 변환했다.
- 업무 처리: 액션의 value에서 seq를 읽고 actionId가 reject인지 approve인지 판단해 배송 팁 상태와 처리 사용자를 갱신했다.
- 메시지 갱신: 기존 메시지의 첫 블록을 제거하고 승인 또는 거부 문구가 담긴 SectionBlock을 같은 위치에 추가했다.
- 응답 정책: replaceOriginal이 true면 기존 메시지를 교체하고 false면 아래에 새 메시지를 붙인다. 승인 UI가 다시 눌리지 않도록 true를 사용했다.
- 최종 전송: BlockActionPayload가 제공한 response_url에 ActionResponseSender로 수정된 블록을 보내 Slack 화면을 갱신했다.
왜 읽나Slack을 알림 채널을 넘어 승인·처리 인터페이스로 쓰려는 Java 백엔드 개발자가 요청·응답 전체 흐름을 구현할 때 참고하기 좋다.