Node.js · 2026-03-21

웹 훅 설계와 보안: 재시도·검증·서명 처리

웹 훅의 재시도 정책과 페이로드 무결성 검증, HMAC 서명 처리 및 재시도 전략을 Node.js 예제로 설명하는 설계

작성일 : 2026-03-21 ㆍ 작성자 : 관리자
post
목차

개요

웹 훅은 이벤트 기반 통신을 간단하게 만든다. 그러나 신뢰성과 보안은 별개로 설계해야 한다. 전송 실패, 중복 수신, 변조 시도까지 고려해야 한다. 이 글은 재시도 정책과 검증, 서명 처리 흐름을 Node.js 예제로 풀어서 설명한다. 처음 접하는 개발자도 이해하기 쉽게 단계별로 정리한다.

핵심 문제 정리

웹 훅에서 자주 발생하는 문제는 다음과 같다.

  • 수신 실패로 인한 데이터 누락
  • 중복 전송으로 인한 중복 처리
  • 페이로드 변조 및 위조 요청
  • 타임 윈도우 내 재생 공격

이를 해결하려면 재시도 로직, 서명 검증, 타임스탬프 확인, idempotency(멱등성) 보장이 필요하다.

재시도 전략

상태 코드 기반 처리

서버는 HTTP 상태 코드로 재시도 여부를 알린다. 2xx는 성공, 4xx는 클라이언트 오류로 재시도하지 않음, 5xx는 서버 오류로 재시도 허용이 일반적이다. 다만 429(Too Many Requests)는 제한 정책에 따라 재시도 간격을 열어줘야 한다.

백오프와 최대 재시도 횟수

재시도는 고정 간격보다 점진적으로 늘리는 백오프 방식이 안전하다. 지수 백오프와 약간의 무작위 지연(jitter)을 섞으면 폭주를 억제한다. 예시 전략은 다음과 같다.

  • 초기 대기: 1초
  • 지수 증가: 2배씩 증가
  • 최대 재시도: 보통 5~8회
  • 총 대기 시간 제한: 서비스 요구에 맞게 설정

멱등성 처리

재시도 시 중복 처리 방지를 위해 멱등 키를 사용한다. 발신자가 각 이벤트에 고유 ID를 붙이면 수신자는 해당 ID를 저장해 이미 처리한 이벤트는 무시한다. 데이터베이스나 캐시(Redis 등)에 짧은 TTL로 기록하는 방식이 일반적이다.

검증과 서명

HMAC 서명 개요

가장 널리 쓰이는 방법은 HMAC-SHA256 등 대칭 서명이다. 발신자는 비밀키로 페이로드를 서명해 헤더에 포함한다. 수신자는 같은 비밀키로 검증해 변조 여부를 확인한다. 타임스탬프를 함께 보내면 재생 공격을 막을 수 있다.

권장 헤더 설계

  • X-Signature: HMAC 값
  • X-Signature-Version: 버전 관리
  • X-Request-Timestamp: UTC 타임스탬프
  • X-Webhook-Id: 이벤트 고유 ID

Node.js 예제

아래 예제는 발신자에서 HMAC을 생성하는 코드와 수신자에서 검증하는 코드다. 서명에 타임스탬프와 페이로드를 조합해 사용한다.

const crypto = require('crypto');

// 발신자: 페이로드 서명 생성
function signPayload(secret, payload, timestamp) {
  const data = `${timestamp}.${JSON.stringify(payload)}`;
  return crypto.createHmac('sha256', secret).update(data).digest('hex');
}

// 사용 예
const secret = 'your_secret_here';
const payload = { event: 'order.created', id: 'evt_123', data: { amount: 1000 } };
const timestamp = Math.floor(Date.now() / 1000);
const signature = signPayload(secret, payload, timestamp);
console.log('X-Signature:', signature);
const express = require('express');
const crypto = require('crypto');
const app = express();
app.use(express.json());

function safeCompare(a, b) {
  if (a.length !== b.length) return false;
  return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b));
}

app.post('/webhook', (req, res) => {
  const secret = process.env.WEBHOOK_SECRET;
  const signature = req.get('X-Signature') || '';
  const timestamp = req.get('X-Request-Timestamp');
  const id = req.get('X-Webhook-Id');

  if (!timestamp || !id) return res.status(400).send('missing headers');

  // 타임 윈도우 검증 (예: 5분)
  const now = Math.floor(Date.now() / 1000);
  if (Math.abs(now - Number(timestamp)) > 300) return res.status(400).send('timestamp out of range');

  const data = `${timestamp}.${JSON.stringify(req.body)}`;
  const expected = crypto.createHmac('sha256', secret).update(data).digest('hex');

  if (!safeCompare(expected, signature)) return res.status(401).send('invalid signature');

  // 멱등성 체크 (예: Redis에 id 저장)
  // 이미 처리된 id면 200 응답

  // 정상 처리
  res.status(200).send('ok');
});

app.listen(3000);

운영 고려사항

로그는 서명 실패, 타임스탬프 오류, 재시도 횟수를 기록해야 한다. 또한 실패 원인에 따라 모니터링 알람을 달리 구성한다. 비밀키는 주기적으로 롤링할 수 있도록 버전 관리 헤더를 둔다. 롤링 시 발신자와 수신자가 병행 검증을 할 수 있도록 이전 키를 일정 기간 유지한다.

요약

안전한 웹 훅 시스템은 다음을 필요로 한다.

  • 명확한 재시도 정책(상태 코드, 백오프, 최대 재시도)
  • 멱등성 보장(이벤트 ID와 TTL 저장)
  • HMAC 기반 서명과 타임스탬프 검증
  • 키 롤링과 로깅 및 모니터링

이 구조를 따르면 전송 실패와 보안 위협을 효과적으로 줄일 수 있다. Node.js 환경에서는 위 예제를 바탕으로 필요에 맞게 확장하면 된다.

Node.js webhook 검증 방법 웹훅 서명 검증 Node 웹훅 재시도 전략 Node.js 웹훅 보안 HMAC 검증 멱등성 처리 재시도 백오프 타임스탬프 검증