웹훅 완벽 가이드: 서명된 페이로드와 재시도 전략
사용자의 엔드포인트로 웹훅을 보내는 API를 구축했습니다. 하지만 곧 고전적인 문제에 직면합니다: 페이로드가 변조되지 않았음을 어떻게 보장할까? 엔드포인트가 다운되면 어떻게 될까? 중복 처리를 어떻게 방지할까? 이 글에서는 서명된 페이로드와 강력한 재시도 전략으로 이러한 과제를 정면으로 다룹니다.
웹훅에 서명이 필요한 이유
웹훅은 다른 시스템에 이벤트를 알리는 HTTP 콜백입니다. 공용 인터넷을 통해 전송되므로 가로채기와 스푸핑에 취약합니다. 서명이 없으면 악의적인 행위자가 사용자에게 가짜 웹훅을 보내 의도하지 않은 작업을 트리거할 수 있습니다. 공유 비밀 키로 페이로드에 서명하면 진위성과 무결성을 보장할 수 있습니다.
서명된 페이로드의 작동 방식
가장 일반적인 접근 방식은 SHA-256을 사용한 HMAC(해시 기반 메시지 인증 코드)입니다. 송신자와 수신자는 비밀 키를 공유합니다. 송신자는 페이로드(때로는 타임스탬프)에 대한 서명을 계산하여 헤더에 포함합니다. 수신자는 서명을 다시 계산하고 수신된 서명과 비교합니다.
단계별: 페이로드 서명하기
- 페이로드 생성: 이벤트 데이터를 JSON으로 직렬화합니다. 서명은 정확한 바이트에 대해 계산되므로 일관된 형식을 유지하세요(예: 추가 공백 없음).
- 타임스탬프 추가: 재전송 공격을 방지하기 위해 페이로드나 헤더에 Unix 타임스탬프를 포함합니다. 수신자는 몇 분 이상 지난 요청을 거부할 수 있습니다.
- HMAC 계산: 비밀 키를 사용하여 페이로드 문자열(또는 타임스탬프 + 페이로드)의 HMAC-SHA256을 생성합니다.
- 서명 첨부:
X-Signature와 같은 헤더로 전송합니다.
다음은 Python 예제입니다:
import hmac
import hashlib
import json
import time
secret = b'your_shared_secret'
payload = {"event": "user.created", "data": {"id": 123}}
body = json.dumps(payload, separators=(',', ':'))
timestamp = str(int(time.time()))
signed_content = timestamp + '.' + body
signature = hmac.new(secret, signed_content.encode(), hashlib.sha256).hexdigest()
# Send headers: X-Signature: signature, X-Timestamp: timestamp
서명 검증하기
수신 측에서는 다음을 수행해야 합니다:
- 헤더에서 타임스탬프와 서명을 추출합니다.
- 재전송 공격을 방지하기 위해 타임스탬프가 너무 오래된 경우(예: 5분 초과) 거부합니다.
- 동일한 방법으로 서명을 다시 계산합니다.
- 타이밍 공격을 피하기 위해 상수 시간 함수를 사용하여 비교합니다.
Node.js 검증 예제:
const crypto = require('crypto');
function verifySignature(req, secret) {
const signature = req.headers['x-signature'];
const timestamp = req.headers['x-timestamp'];
if (!signature || !timestamp) return false;
if (Date.now() / 1000 - timestamp > 300) return false; // 5 min
const body = JSON.stringify(req.body);
const signed = timestamp + '.' + body;
const expected = crypto.createHmac('sha256', secret).update(signed).digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
재시도 전략 설계하기
서명이 있어도 네트워크 문제, 서버 오류 또는 시간 초과로 인해 웹훅이 실패할 수 있습니다. 강력한 재시도 전략은 수신자에게 과부하를 주지 않으면서 전달을 보장합니다.
주요 고려 사항
- 멱등성: 수신자는 중복 이벤트를 원활하게 처리해야 합니다. 멱등성 키(예: 이벤트 ID)를 사용하여 중복을 제거하세요.
- 지수 백오프: 수신자가 복구할 시간을 주기 위해 증가하는 지연 시간(예: 1초, 5초, 30초, 5분, 30분) 후에 재시도합니다.
- 최대 재시도 횟수: 제한(예: 5회)을 설정한 후 웹훅을 실패로 표시하고 사용자에게 알릴 수 있습니다.
- 시간 초과: 각 시도에 합리적인 시간 초과(예: 10초)를 설정합니다.
재시도 구현하기
대부분의 웹훅 송신자는 큐 시스템(예: Redis, RabbitMQ 또는 데이터베이스)을 사용하여 재시도를 예약합니다. 다음은 간단한 흐름입니다:
- 이벤트가 트리거되면 페이로드와 대상 URL을 포함한 웹훅 작업을 큐에 넣습니다.
- 작업자가 작업을 가져와 전달을 시도합니다.
- 응답 상태가 2xx이면 성공으로 표시합니다.
- 그렇지 않으면 최대 시도 횟수까지 지수 백오프로 재시도를 예약합니다.
- 최대 시도 후 실패로 표시하고 알립니다.
재시도 일정 예시:
| 시도 | 지연 |
|---|---|
| 1 | 즉시 |
| 2 | 1분 |
| 3 | 5분 |
| 4 | 30분 |
| 5 | 2시간 |
웹훅 보안 모범 사례
- HTTPS 사용: 도청을 방지하기 위해 항상 TLS를 통해 웹훅을 전달하세요.
- 비밀 키 교체: 공유 비밀 키를 주기적으로 교체하고 교체 중에는 여러 활성 키를 지원하세요.
- URL 검증: 웹훅 URL이 HTTPS이고 내부 IP를 가리키지 않는지 확인하세요(SSRF 방지).
- 로그 및 모니터링: 디버깅 및 감사를 위해 전달 시도와 실패를 기록하세요.
FAQ
서명된 웹훅 페이로드란 무엇인가요?
서명된 페이로드에는 암호화 서명(보통 HMAC)이 포함되어 있어 수신자가 송신자의 진위성과 페이로드가 변조되지 않았음을 확인할 수 있습니다.
실패한 웹훅을 몇 번 재시도해야 하나요?
만능 답은 없지만 일반적인 접근 방식은 지수 백오프를 사용하여 3~5회 재시도하는 것입니다. 이벤트의 중요성과 수신자의 처리 능력을 고려하세요.
웹훅 서명에 JWT를 사용할 수 있나요?
예, JWT를 사용할 수 있지만 더 복잡합니다. HMAC은 더 간단하며 대부분의 사용 사례에 충분합니다. JWT는 서명 자체에 클레임이나 만료를 포함해야 할 때 유용합니다.
웹훅 페이로드를 테스트할 준비가 되셨나요? JSON Formatter를 사용하여 전송 전에 JSON 페이로드를 검증하고 예쁘게 출력하세요.