웹페이지 또는 웹앱에서 발생하는 특정 행동(이벤트)들을 커스텀 Callback으로 변환해주는 방법이다. 즉, 데이터가 변경되었을 때 실시간으로 알림을 받을 수 있는 기능이다. 클라이언트가 서버에게 웹훅을 받을 Callback URL을 제공하고 받고 싶은 이벤트를 등록한다. 이후 서버에서 특정 행동(이벤트)이 트리거 되면 클라이언트는 제공한 Callback URL로 이벤트 데이터를 받을 수 있다.
Polling과 Webhook의 차이점?

폴링 방식은 특정 행동(이벤트)이 발생했는지 주기적으로 확인해야 한다. 반면에 웹훅은 한 번 설정하면 클라이언트가 추가 요청을 보내지 않아도 된다.
즉, 폴링은 “너 준비됐어?” 처럼 상대방을 직접 확인하는 방식이고 웹훅은 “준비되면 알려줘”와 같이 상대방에게 통보 후 기다리는 방식이다.
디스코드에서 Webhook을 설정하는 방법은 다음과 같다.

먼저 디스코드 서버를 생성한 후 원하는 채널을 만든다.

이 글에서는 회원가입 이벤트 알림을 구현할 예정이기 때문에 위와 같은 채널을 만들었다. 채널이 생성되면 채널 편집으로 들어간다.

그러면 위와 같이 연동 탭을 확인할 수 있다. 연동 탭에 들어가면 웹후크 옵션이 존재하며 해당 옵션으로 들어가면 된다.

웹후크 옵션으로 들어가면 새 웹후크를 만들 수 있는 버튼이 보여지게 된다. 해당 버튼으로 원하는 이름의 웹후크 봇을 생성하면 된다. 이때 웹후크 URL은 Callback URL에 해당된다.
이제 클라이언트(디스코드)의 웹훅 설정은 완료되었다. 남은 작업은 해당 클라이언트에 이벤트 메시지를 전달할 서버에서 진행하면 된다.
스프링 부트 애플리케이션에서 구현한 코드는 다음과 같다.
DiscordMessage
public record DiscordMessage(
String content
) {
public static DiscordMessage createDiscordMessage(String message) {
return new DiscordMessage(message);
}
}
클라이언트(디스코드)에 전달할 이벤트 메시지 클래스이다.
DiscordFeignClient
@FeignClient(name = "${discord.name}", url = "${discord.webhook-url}")
public interface DiscordFeignClient {
@PostMapping(produces = MediaType.APPLICATION_JSON_VALUE)
void sendMessage(@RequestBody DiscordMessage discordMessage);
}
클라이언트(디스코드)에 이벤트 메시지를 전달하기 위해선 HTTP 통신이 필요하다. 스프링 부트 애플리케이션에서 HTTP Client 도구로 Open Feign을 활용하였고 해당 Client를 통해 서버(스프링 부트) ↔ 클라이언트(디스코드) 간 HTTP 통신을 수행하게 된다.
application.yml
discord:
name: discord-feign-client
webhook-url: # 디스코드 웹후크 URL을 작성하면 된다.
DiscordMessageProvider
@RequiredArgsConstructor
@Component
public class DiscordMessageProvider {
private final DiscordFeignClient discordFeignClient;
public void sendMessage(EventMessage eventMessage) {
DiscordMessage discordMessage = createDiscordMessage(eventMessage.getMessage());
sendMessageToDiscord(discordMessage);
}
private void sendMessageToDiscord(DiscordMessage discordMessage) {
try {
discordFeignClient.sendMessage(discordMessage);
} catch (FeignException e) {
throw new InvalidValueException(ErrorMessage.INVALID_DISCORD_MESSAGE);
}
}
}
원하는 메시지를 클라이언트(디스코드)에 맞는 이벤트 메시지 객체로 변환하여 HTTP 통신을 수행하는 역할을 하는 클래스이다. 즉, 디스코드 웹훅을 핸들링하는 클래스이다.
EventMessage
@RequiredArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public enum EventMessage {
SIGN_UP_EVENT("doorip 서비스에 회원가입 이벤트가 발생했습니다. 🎉");
private final String message;
}
이벤트 메시지를 관리하는 Enum 클래스이다.
UserService
@RequiredArgsConstructor
@Transactional
@Service
public class UserService {
...
private final DiscordMessageProvider discordMessageProvider;
...
public UserSignUpResponse signUp(String token, UserSignUpRequest request) {
Platform enumPlatform = getEnumPlatformFromStringPlatform(request.platform());
String platformId = getPlatformId(token, enumPlatform);
validateDuplicateUser(enumPlatform, platformId);
User savedUser = saveUser(request, platformId, enumPlatform);
Token issueToken = jwtProvider.issueToken(savedUser.getId());
updateRefreshToken(issueToken.refreshToken(), savedUser);
// 회원가입이 완료되면 트리거 된다.
discordMessageProvider.sendMessage(EventMessage.SIGN_UP_EVENT);
return UserSignUpResponse.of(issueToken, savedUser.getId());
}
}
다음은 UserService의 비즈니스 로직 중 회원가입에 해당하는 부분이다. 해당 부분에서 회원가입이 완료되면 DiscordMessageProvider를 통해 EventMessage에 등록한 이벤트 메시지 중 원하는 메시지를 매개변수로 전달하여 최종적으로 디스코드에 이벤트 메시지가 전달된다.

그러면 위와 같이 디스코드에서 원하는 이벤트 메시지 알림을 받을 수 있게 된다.