5분 안에 프로덕션 레디 웹훅 전송 시스템 구축

발행: (2025년 12월 13일 오후 10:08 GMT+9)
9 min read
원문: Dev.to

Source: Dev.to

작동 방식: 흐름

Webhook 전달 흐름

핵심 기능

  • ⚡ 즉시 응답 (202 Accepted)
  • 🔄 모든 구독자에게 병렬 전달
  • 🔐 모든 웹훅에 HMAC SHA‑256 서명
  • ♻️ 지수 백오프를 이용한 자동 재시도 (1 초, 2 초, 4 초)
  • 📊 상태 모니터링 및 10회 실패 시 자동 비활성화

아웃고잉 웹훅이란?

대부분의 개발자는 Stripe, GitHub, Slack 등에서 수신하는 웹훅에 익숙합니다. 아웃고잉 웹훅(‘리버스 웹훅’이라고도 함)은 이벤트가 발생했을 때 애플리케이션이 외부 서비스에 알릴 수 있게 해줍니다. 사용자가 가입하거나, 주문이 발송되거나, 결제가 완료되는 등 시스템에서 무언가가 일어나면 애플리케이션은 등록된 웹훅 URL로 HTTP POST 요청을 보냅니다.

이는 통합을 구축하고, 실시간 알림을 제공하며, 고객이 플랫폼을 확장할 수 있게 하는 데 필수적입니다.

도전 과제: 제대로 구축하기는 어렵다

처음부터 견고한 웹훅 전달 시스템을 만들려면 여러 복잡한 문제를 해결해야 합니다.

  • URL 검증 – 웹훅 URL이 정상인지 확인
  • 보안 – 변조 방지를 위한 HMAC 서명
  • 신뢰성 – 다운된 엔드포인트를 우아하게 처리
  • 확장성 – 수천 명의 구독자에게 차단 없이 전달
  • 모니터링 – 전달 통계, 실패 추적, 고장난 웹훅 자동 비활성화
  • 재시도 로직 – 지수 백오프, 최대 시도 횟수, 타임아웃 처리

큐 기반 아키텍처: 비밀 소스

확장 가능한 웹훅 전달의 핵심은 이벤트 트리거와 전달을 분리하는 것입니다.

// 1. 감사 로그를 위해 이벤트 저장
await conn.insertOne('events', eventData);

// 2. 일치하는 모든 웹훅에 이벤트 ID 표시
await conn.updateMany(
  'webhooks',
  { status: 'active', $or: [{ events: eventType }, { events: '*' }] },
  { $set: { pendingEventId: eventData.id } }
);

// 3. 하나의 원자적 작업으로 모든 웹훅을 큐에 넣기
await conn.enqueueFromQuery(
  'webhooks',
  { status: 'active', pendingEventId: eventData.id },
  'webhook-delivery'
);

마법은 enqueueFromQuery 에 있습니다. 웹훅을 메모리로 로드하고 반복하는 대신, 다음을 수행합니다.

  • 단일 데이터베이스 작업으로 전체를 큐에 삽입
  • 메모리 오버헤드 없이 수천 개의 웹훅 처리
  • 즉시 반환 (202 Accepted)
  • 워커 함수가 병렬로 전달을 처리

이 아키텍처는 구독자 목록이 방대해도 즉시 응답 시간을 보장합니다.

보안 우선: HMAC 서명

각 웹훅 페이로드에는 수신자가 진위 여부를 검증할 수 있도록 암호화 서명이 포함됩니다.

function generateSignature(payload, secret) {
  const timestamp = Math.floor(Date.now() / 1000);
  const sigBasestring = `${timestamp}.${payload}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(sigBasestring, 'utf8')
    .digest('hex');
  return { signature: `v1=${signature}`, timestamp };
}

각 웹훅에 포함되는 헤더

  • X-Webhook-Signature: HMAC SHA‑256 서명
  • X-Webhook-Timestamp: Unix 타임스탬프 (재생 공격 방지)
  • X-Webhook-Id: 구독 ID

수신자는 서명을 검증하고, 5분 이상 지난 타임스탬프는 거부하여 재생 공격을 방지합니다.

자동 URL 검증

웹훅 등록을 받기 전에 시스템은 업계 표준 방법으로 URL을 검증합니다.

  • Stripe‑스타일 검증 – 검증 토큰이 포함된 테스트 페이로드를 보내고 HTTP 200 응답을 기대
  • Slack‑스타일 챌린지 – 무작위 챌린지 문자열을 보내고 응답에 그대로 반환되길 기대

검증은 워커 큐에서 비동기로 실행되므로, 등록 요청은 즉시 반환되고 검증은 백그라운드에서 진행됩니다.

스마트 재시도 로직

전송 실패 시 지수 백오프를 적용한 자동 재시도가 실행됩니다.

{
  "maxRetries": 3,
  "backoffIntervals": ["1s", "2s", "4s"],
  "timeout": "10s",
  "autoDisableAfter": 10
}

크론 작업이 30분마다 실행되어 1시간 이상 실패한 웹훅을 재시도함으로써, 고장난 엔드포인트에 대한 과도한 요청을 방지합니다.

이벤트 유연성

일부 시스템이 미리 정의된 이벤트 타입만 허용하는 것과 달리, 이 시스템은 어떤 이벤트 이름이든 지원합니다.

# 전자상거래 이벤트
POST /events/trigger/order.placed

# IoT 이벤트
POST /events/trigger/sensor.temperature.high

# 맞춤 비즈니스 이벤트
POST /events/trigger/report.generated

구독자는 특정 이벤트에만 등록하거나 "*" 와일드카드를 사용해 모든 이벤트를 받을 수 있습니다.

프로덕션 수준 모니터링

시스템은 각 웹훅에 대한 상세 통계를 추적합니다.

{
  "deliveryCount": 1247,
  "consecutiveFailures": 0,
  "lastDeliveryAt": "2025-01-15T10:30:00Z",
  "lastDeliveryStatus": "success",
  "status": "active"
}

연속 10번 실패하면 웹훅이 자동으로 비활성화되어 리소스를 절약합니다. 사용자는 POST /webhooks/:id/retry 로 수동 재시도도 할 수 있습니다.

전체 API

웹훅 관리를 위한 완전한 CRUD API가 제공됩니다.

# 웹훅 생성 (자동 검증 포함)
POST /webhooks
{
  "clientId": "customer-123",
  "url": "https://example.com/webhook",
  "events": ["order.placed", "order.shipped"],
  "verificationType": "stripe"
}

# 웹훅 목록 조회
GET /webhooks?status=active&event=order.placed

# 이벤트 트리거 (모든 구독자에게 전달 큐에 넣음)
POST /events/trigger/order.placed
{
  "orderId": "123",
  "total": 99.99,
  "customer": "john@example.com"
}

# 전달 통계 확인
GET /webhooks/:id/stats

# 실패한 웹훅 수동 재시도
POST /webhooks/:id/retry

멱등성 등록

시스템은 clientId + url 을 복합 키로 사용해 upsert 로직을 구현합니다.

  • 첫 번째 등록 시 웹훅이 생성됩니다.
  • 동일한 clientId + url 로 재등록하면 기존 웹훅이 업데이트됩니다.
  • 중복 웹훅을 방지합니다.
  • 업데이트 시에도 HMAC 비밀키가 유지됩니다.
  • 다중 테넌트 SaaS 애플리케이션에 최적입니다.

실제 적용 사례

이 시스템은 인기 워크플로우 도구와 원활히 통합됩니다.

  • n8n – 웹훅 트리거를 만들고 URL을 등록하면 워크플로가 자동으로 활성화됩니다.
  • Zapier – “Webhooks by Zapier”의 Catch Hook 를 사용하고 verificationType: "stripe" 로 URL을 등록하면 Zap이 작동합니다.
  • 맞춤 앱 – 어떤 언어든 서명 검증을 구현하고 바로 이벤트를 받을 수 있습니다.

실제 동작 확인

직접 사용해 보고 싶나요? 전체 구현은 Codehooks.io 템플릿으로 제공됩니다.

Back to Blog

관련 글

더 보기 »