웹훅 완벽 가이드: 서명된 페이로드와 재시도 전략

Backend2026-10-30TryQuickToolBox

API를 구축했고 이제 이벤트가 발생할 때 다른 서비스에 알려야 합니다. 폴링은 비효율적이므로 웹훅을 구현합니다. 그러나 곧 두 가지 일반적인 문제에 직면합니다: 페이로드가 변조되지 않았음을 어떻게 보장할 것인가, 그리고 수신 엔드포인트가 다운되었을 때 어떻게 할 것인가. 이 글에서는 서명된 페이로드와 재시도 전략을 사용하여 웹훅을 안전하고 안정적으로 만드는 방법을 설명합니다.

웹훅에 서명과 재시도가 필요한 이유

웹훅은 이벤트에 의해 트리거되는 HTTP 콜백입니다. 구현은 간단하지만 보안 및 안정성 문제를 야기합니다. 서명이 없으면 공격자가 엔드포인트에 요청을 위조하여 데이터 손상이나 권한 없는 작업을 초래할 수 있습니다. 재시도가 없으면 일시적인 실패(예: 네트워크 장애 또는 일시적인 서버 오류)로 인해 이벤트를 놓치게 됩니다.

서명된 페이로드는 진위성을 해결합니다: 발신자가 비밀 키로 페이로드에 서명하고 수신자가 이를 검증합니다. 재시도는 안정성을 해결합니다: 발신자가 실패한 전달을 백오프와 함께 재시도합니다.

서명된 페이로드의 작동 방식

서명된 페이로드에는 요청 본문과 공유 비밀에서 생성된 암호화 서명이 포함됩니다. 가장 일반적인 방법은 SHA-256을 사용하는 HMAC(해시 기반 메시지 인증 코드)입니다.

서명 생성

발신자는 비밀 키를 사용하여 원시 요청 본문의 HMAC을 계산합니다. 서명은 종종 재생 공격을 방지하기 위해 타임스탬프와 함께 헤더로 전송됩니다.

// Node.js 예제: HMAC 서명 생성
const crypto = require('crypto');

function signPayload(payload, secret) {
  const timestamp = Math.floor(Date.now() / 1000);
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  return { timestamp, signature };
}

타임스탬프와 서명을 헤더에 포함하세요. 예: X-Webhook-Timestamp 및 X-Webhook-Signature.

서명 검증

수신자는 수신된 타임스탬프와 원시 본문을 사용하여 서명된 페이로드를 재구성한 다음 HMAC을 계산하고 안전하게 비교해야 합니다.

// Node.js 예제: 서명 검증
function verifySignature(rawBody, timestamp, receivedSignature, secret) {
  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  // 타이밍 안전 비교 사용
  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );
}

타이밍 공격을 방지하려면 항상 타이밍 안전 비교를 사용하세요. 또한 재생 공격을 완화하려면 타임스탬프가 최근인지(예: 5분 이내) 확인하세요.

재시도 전략 설계

재시도는 안정적인 웹훅 전달에 필수적입니다. 그러나 순진한 재시도는 수신자를 압도하거나 중복 처리를 유발할 수 있습니다. 좋은 재시도 전략에는 다음이 포함됩니다:

다음은 의사 코드로 작성된 간단한 재시도 루프입니다:

function deliverWithRetry(url, payload, maxRetries = 5) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await fetch(url, { method: 'POST', body: payload });
      if (response.ok) return;
    } catch (error) {
      // 오류 로깅
    }
    attempt++;
    const delay = Math.pow(2, attempt) * 1000; // 지수 백오프
    await new Promise(resolve => setTimeout(resolve, delay));
  }
  // 데드 레터 큐로 이동
}

썬더링 허드 문제를 피하기 위해 백오프에 지터를 추가하는 것을 고려하세요.

비교: 폴링 vs 재시도가 있는 웹훅

측면 폴링 재시도가 있는 웹훅
지연 시간 높음 (간격에 따라 다름) 낮음 (거의 실시간)
서버 부하 일정함 (이벤트가 없을 때도) 이벤트 기반
안정성 높음 (수신자가 제어) 재시도 전략에 따라 다름
보안 표준 API 인증 서명된 페이로드 필요

단계별: 서명과 재시도를 사용한 웹훅 구현

  1. 각 웹훅 엔드포인트에 대한 비밀 키 생성 및 안전하게 공유.
  2. 이벤트 발생 시, 페이로드(JSON)를 구성하고 비밀과 타임스탬프를 사용하여 HMAC-SHA256으로 서명을 계산.
  3. POST 요청 전송 시 헤더 포함: Content-Type: application/json, X-Webhook-Timestamp, X-Webhook-Signature.
  4. 수신자 측에서, 원시 본문과 타임스탬프를 사용하여 서명을 검증. 유효하지 않거나 타임스탬프가 너무 오래된 경우 거부.
  5. 전달 실패 시 (비 2xx 응답 또는 네트워크 오류), 최대 시도 횟수까지 지수 백오프로 재시도.
  6. 최대 재시도 후, 실패를 기록하고 선택적으로 경고; 수동 재생을 위해 이벤트를 데드 레터 큐에 저장.
  7. 수신자 측에서 멱등성 보장을 위해 이벤트 ID를 사용하여 중복 무시.

일반적인 함정 및 모범 사례

FAQ

서명된 웹훅 페이로드란 무엇인가요?

서명된 웹훅 페이로드에는 암호화 서명(예: HMAC)이 포함되어 있어 수신자가 요청이 예상 발신자로부터 시작되었고 변조되지 않았음을 확인할 수 있습니다.

실패한 웹훅을 몇 번 재시도해야 하나요?

일반적인 관행은 지수 백오프를 사용하여 3~5회 재시도하는 것입니다. 정확한 횟수는 SLA와 이벤트의 중요성에 따라 다릅니다. 재시도 제한을 초과한 이벤트에는 항상 데드 레터 큐를 사용하세요.

서명 없이 웹훅을 사용할 수 있나요?

권장하지 않습니다. 서명이 없으면 엔드포인트가 위조 요청에 취약합니다. 항상 서명(예: HMAC)을 사용하고 재생 공격을 방지하기 위해 타임스탬프를 검증하세요.

웹훅 전달을 디버깅할 때 서버 로그를 검사하는 것이 중요합니다. Nginx를 사용하는 경우 Nginx Log Analyzer를 사용하여 액세스 로그를 구문 분석하고 웹훅 요청을 추적하며 실패를 식별할 수 있습니다.