본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 30. 03:26

Spring Boot에서의 HeyGen API 구현: Webhook을 이용한 AI 비디오 생성

요약

Spring Boot와 Kotlin을 사용하여 HeyGen API를 통합하고 Webhook을 통해 AI 비디오 생성 알림을 받는 방법을 설명합니다. API 설정부터 REST 클라이언트 구현, 비동기 처리를 위한 DTO 설계까지의 과정을 다룹니다.

핵심 포인트

  • Spring Boot 환경에서 HeyGen API 통합 방법 안내
  • 환경 변수를 활용한 안전한 API 키 관리법
  • 비동기 비디오 생성을 위한 Webhook 처리 구현
  • Kotlin 기반의 REST 클라이언트 및 DTO 설계 가이드

Hey(Gen)! 이 글에서는 AI 비디오를 생성하고 Webhook 알림을 받기 위해 Kotlin으로 작성된 Spring Boot 애플리케이션에 HeyGen API를 통합하는 방법을 보여드리겠습니다.

저는 원래 이 통합 기능을 저의 AI 산타 인사 생성기(AI Santa greeting generator)를 위해 구축했습니다. 프로젝트 자체에 관심이 있으시다면, 아이디어, 기술 스택(tech stack) 및 마케팅 계획을 다룬 별도의 글을 여기에서 확인하실 수 있습니다.

시작해 봅시다!

HeyGen 설정하기

먼저, HeyGen에서 제공하는 값으로 설정 파일을 업데이트하세요. 이 경우에는 application.yaml에 설정을 추가하겠습니다.

heygen:
  base-url: https://api.heygen.com
  api-key: ${HEYGEN_API_KEY}
...

비밀 정보(secrets)를 application.yaml 파일에 직접 넣지 마세요. 항상 환경 변수(environment variables)를 사용해야 합니다. 또한, API URL은 변경될 수 있으므로 나중에 골치 아픈 일이 생기지 않도록 HeyGen이 현재 무엇을 사용하는지 확인하세요.

다음으로, 설정 값을 깔끔하게 가져오기 위해 @ConfigurationProperties 클래스를 생성합니다.

import org.springframework.boot.context.properties.ConfigurationProperties

@ConfigurationProperties("heygen")
...

REST 클라이언트 설정하기

HeyGen API에 요청을 보내려면 REST 클라이언트를 생성해야 합니다. 제 프로젝트에서는 모든 빈(bean)을 등록하는 BeanConfig라는 별도의 @Configuration 클래스를 사용합니다.

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpHeaders
...

요청 및 응답 DTO

비디오 생성 DTO

저는 비디오 생성 요청을 보내고 HeyGen으로부터 응답으로 videoId를 받기 위해 이 DTO들을 사용했습니다. 비디오 생성 프로세스는 비동기(asynchronously)로 진행되므로, 처음에는 비디오의 ID만 받게 됩니다.

import com.fasterxml.jackson.annotation.JsonProperty
import org.stitas.dovkal.enums.SantaType

...

비디오 조회 DTO

비디오 생성이 완료된 후 videoId를 통해 비디오 데이터를 가져오기 위해 다음과 같은 DTO를 사용했습니다. 저의 경우에는 videoUrl만 필요했지만, HeyGen은 필요에 따라 추가로 받을 수 있는 더 많은 데이터를 반환합니다.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty

...

Webhook DTOs

마지막으로, 이는 Webhook (웹훅) 이벤트를 수신하기 위해 사용한 DTO들입니다.

먼저, 페이로드 (payload) 를 문자열로 수신한 다음 HeyGenWebhookEnvelopeDto로 변환을 시도합니다. 그 후, eventData 필드를 AvatarVideoEventDataDto 클래스로 변환합니다. 이렇게 하는 이유는 서명 (signature) 을 검증하기 위해 먼저 전체 eventData 객체가 필요하기 때문입니다.

import com.fasterxml.jackson.annotation.JsonIgnoreProperties
import com.fasterxml.jackson.annotation.JsonProperty
import tools.jackson.databind.JsonNode
...

HeyGen API 클라이언트 구현하기

HeyGen과 통신하기 위해서는 우리가 생성한 DTO에 데이터를 채우고, 설정된 REST 클라이언트를 사용하여 전송해야 합니다.

이 서비스는 정확히 그 역할을 수행합니다. 저는 두 개의 public 메서드를 생성했습니다.

비디오 생성 요청 보내기

fun sendVideoGenerationRequest(santaType: SantaType, message: String, locale: String): String {
    log.info("Sending video generation request to HeyGen for santaType={}", santaType.name)
    val requestBody = VideoGenerationRequestDto(
...

여기서 santaType은 비즈니스 로직을 담고 있는 제 애플리케이션의 커스텀 enum (열거형) 입니다. 이 메서드에서는 enum의 avatarId 속성을 사용하는데, 이는 제가 HeyGen UI를 통해 생성한 아바타의 하드코딩된 ID 값입니다.

생성된 비디오 URL 가져오기

fun getVideoUrl(videoId: String): String {
    val response = heyGenRestClient.get()
        .uri("/v3/videos/${videoId}")
...

HeyGen이 자신들의 측에 비디오를 오랫동안 저장해 준다는 점은 매우 편리합니다. 덕분에 저는 S3 스토리지 서비스를 구축하거나 비용을 지불할 필요 없이, 비디오 URL을 직접 가져올 수 있습니다.

Helper methods (헬퍼 메서드)

API 클라이언트에서 사용되는 헬퍼 메서드(Helper methods)는 다음과 같습니다:

private fun getResponseBody(response: ClientHttpResponse): String {
    return runCatching {
        StreamUtils.copyToString(response.body, StandardCharsets.UTF_8)
...
companion object {
    private val log = LoggerFactory.getLogger(HeyGenApiClient::class.java)
}

또한 저는 제 애플리케이션을 위해 직접 만든 커스텀 HeyGenIntegrationException을 사용하지만, 여기에서 이를 깊게 다루지는 않겠습니다. 단순함을 위해 IllegalStateException과 같은 것을 사용하거나 다른 방식으로 에러를 처리할 수 있습니다.

나중에 저는 Controller -> Service -> Repository 패턴을 따라 HeyGenService 클래스에서 이 메서드들을 사용합니다. 이 경우 API 클라이언트가 리포지토리(Repository) 부분을 대체합니다. 저는 그곳에 추가적인 비즈니스 로직만 추가할 것이므로, 이 부분은 공유하지 않겠습니다.

HeyGen 비디오 생성 이벤트를 위한 웹훅(Webhook) 생성

tired programmer sitting in front of computer with head put on the table

세상에... 구현 과정 중 이 부분이 저를 정말 미치게 만들었습니다. 솔직히 제가 정신이 나가고 있는 줄 알았습니다. 왜 그랬는지 확인하려면 계속 읽어주세요.

웹훅 엔드포인트(Webhook endpoint) 생성

먼저, 이벤트를 수신할 엔드포인트가 필요하므로 컨트롤러(Controller)를 생성해야 합니다.

import org.springframework.http.HttpHeaders
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.annotation.PostMapping
...

이제 여러분은 저에게 이렇게 물으실지도 모릅니다. "왜 동일한 헤더에 대해 세 가지 다른 이름을 확인하고 있나요? HeyGen 문서에서 헤더 이름을 제공하지 않나요?"

네, 그들의 문서에는 헤더 이름이 Heygen-Signature라고 적혀 있습니다. 하지만 몇 시간 동안 디버깅을 하고 제가 미치지 않았다는 것을 스스로에게 되뇌며 확인한 결과, 실제 헤더 이름은 그냥 signature였습니다.

그래서 제 구현에서는 더 이상 그들을 신뢰하지 않기로 했고, 그들이 헤더 이름을 다시 변경할 경우를 대비해 몇 가지 폴백 (fallback) 장치를 남겨두었습니다.

Webhook 처리하기

다음으로, Webhook을 처리해야 합니다. 이를 위해 다음과 같은 메서드를 생성했습니다.

fun handleWebhook(
    payload: String,
    heyGenSignature: String?,
...

이 메서드는 먼저 제공된 서명 (signature)으로 페이로드 (payload)를 검증한 다음, 이벤트 유형에 따라 이벤트를 처리합니다.

HeyGen Webhook 서명 검증하기

private fun validateSignature(
    payload: String,
    heyGenSignature: String,
...

저는 보통 UI 작업에만 AI를 사용하고 백엔드에서는 사용하지 않는데, 그 이유는 백엔드에서는 AI를 충분히 신뢰하지 않기 때문입니다. 하지만 이번 사례는 AI가 빛을 발하는 좋은 예시라고 생각합니다.

이러한 암호화 (cryptography) 메서드들을 일일이 수동으로 알거나 찾아내는 데는 엄청난 시간이 걸렸을 것입니다. AI 덕분에 큰 문제 없이 몇 번의 반복 (iteration)만으로 이를 완성할 수 있었습니다.

Webhook 이벤트 처리하기

여기서는 eventData를 저의 커스텀 DTO로 변환하고, 데이터베이스에서 이벤트를 검증하며, 중복 여부를 확인하는 작업만 수행합니다. 예외적인 상황 (edge case)이긴 하지만, 나중에 후회하는 것보다 미리 방지하는 것이 낫습니다. 나머지는 단순한 비즈니스 로직 (business logic)입니다.

private fun processOrderBasedOnEvent(eventEnvelope: HeyGenWebhookEnvelopeDto, orderStatus: OrderStatus): Order? {
    log.info("Processing order from webhook event={}", eventEnvelope.eventType)

...

HeyGen Webhook 테스트하기

HeyGen은 운영 환경 (production)에서 테스트하는 것을 포함하지 않는 한, 별도의 테스트 환경을 제공하지 않습니다 :D. 따라서 Webhook으로 요청을 수동으로 보내야 합니다.

이를 위해 avatar_video.successavatar_video.fail 유형의 모의 (mock) 이벤트가 포함된 curl 명령어를 생성했습니다.

avatar_video.success

payload='{"event_type":"avatar_video.success","event_data":{"video_id":"videoId"}}'

signature=$(printf '%s' "$payload" | openssl dgst -sha256 -hmac "secret" -hex | sed 's/^.* //')
...

avatar_video.fail

payload='{"event_type":"avatar_video.fail","event_data":{"video_id":"videoId","error":{"code":"mock_failure","message":"Mock HeyGen video generation failed"}}}'

signature=$(printf '%s' "$payload" | openssl dgst -sha256 -hmac "secret" -hex | sed 's/^.* //')
...

마치며

이번 통합 과정은 특히 웹훅 서명 (webhook signature) 문제로 인해 정말 많은 피와 땀, 눈물을 흘리게 했습니다. 이곳에 모든 내용을 문서화해 두었으니, 다른 누군가가 디버깅(debugging)에 소요할 몇 시간을 아낄 수 있기를 바랍니다.

이 글이 유용했다면, stitas.dev에서 제 블로그를 구독해 주세요. 앞으로도 더 실용적인 Spring Boot, Kotlin, 그리고 API 통합 (API integration) 튜토리얼을 공유하겠습니다.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0