웹훅 완벽 가이드: 서명된 페이로드와 재시도 로직
결제 게이트웨이를 막 통합했다고 가정해 봅시다. 문서에는 이렇게 쓰여 있습니다: "결제가 성공하면 웹훅을 보내드립니다." 좋습니다. 그런데 이런 의문이 듭니다. 이 요청이 정말 그쪽에서 온 것인지 어떻게 알 수 있을까? 그들이 보낼 때 내 서버가 다운되어 있으면? 같은 이벤트를 두 번 보내면?
웹훅은 강력하지만 함정이 있습니다. 이 가이드에서는 웹훅을 올바르게 처리하기 위해 필요한 메커니즘, 보안, 안정성 패턴을 살펴봅니다.
웹훅이란 무엇인가?
웹훅은 특정 이벤트가 발생했을 때 한 시스템이 다른 시스템으로 보내는 HTTP POST 요청입니다. 앱이 몇 초마다 API를 폴링하는 대신("새 주문 있나? 새 주문 있나?"), 제공자가 무언가 발생하는 즉시 데이터를 푸시합니다.
일반적인 흐름:
- 제공자에게 URL(예:
https://yourapp.com/webhooks/payments)을 등록합니다. - 이벤트가 발생합니다(결제 성공, 사용자 가입, 파일 업로드).
- 제공자가 이벤트를 설명하는 JSON 본문과 함께 POST 요청을 URL로 보냅니다.
- 서버가 이벤트를 처리하고
2xx로 응답하여 수신을 확인합니다.
빠르게 응답하지 않으면(보통 5~30초 이내), 제공자는 전달이 실패했다고 간주하고 재시도할 수 있습니다.
서명된 페이로드를 반드시 검증해야 하는 이유
웹훅 엔드포인트는 공개적으로 접근 가능합니다. URL을 발견한 누구나 가짜 이벤트를 보낼 수 있습니다. 검증 없이는 공격자가 "결제 성공" 이벤트를 위조하여 무료로 상품을 받을 수 있습니다.
해결책은 서명된 페이로드입니다. 제공자는 공유 비밀(당신과 그들만 아는)을 사용하여 요청 본문에 대한 암호화 서명을 계산하고 헤더에 포함합니다. 당신은 자신의 쪽에서 서명을 다시 계산하고 비교합니다.
HMAC 서명 작동 방식
대부분의 제공자는 HMAC-SHA256을 사용합니다. 단계는 다음과 같습니다:
- 제공자가 원시 요청 본문과 타임스탬프를 연결합니다(재전송 공격 방지).
- 제공자가
HMAC-SHA256(secret, timestamp + '.' + body)를 계산합니다. - 제공자가 서명을 헤더에 보냅니다. 예:
X-Signature: t=1234567890,v1=abc123.... - 타임스탬프와 서명을 추출하고, 비밀의 사본을 사용하여 HMAC을 다시 계산하고, 상수 시간 비교 함수를 사용하여 비교합니다.
다음은 최소한의 Node.js 예제입니다:
const crypto = require('crypto');
function verifySignature(req, secret) {
const signatureHeader = req.headers['x-signature'];
const [tPart, vPart] = signatureHeader.split(',');
const timestamp = tPart.split('=')[1];
const receivedSig = vPart.split('=')[1];
const payload = timestamp + '.' + JSON.stringify(req.body);
const expectedSig = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
// 타이밍 공격을 방지하기 위한 상수 시간 비교
return crypto.timingSafeEqual(
Buffer.from(receivedSig, 'hex'),
Buffer.from(expectedSig, 'hex')
);
}
중요: 서명 검증에는 항상 원시 요청 본문을 사용하세요. 프레임워크가 JSON을 파싱하고 다시 직렬화하면 바이트 시퀀스가 달라져 검증이 실패할 수 있습니다. 파싱 전에 원시 본문을 캡처하세요.
타임스탬프 검증
타임스탬프가 최근인지(예: 5분 이내) 확인하세요. 이는 공격자가 유효한 요청을 캡처하여 나중에 다시 보내는 재전송 공격을 방지합니다.
재시도 로직: 실패를 우아하게 처리하기
서버가 다운되거나 과부하 상태이거나 네트워크 문제가 발생할 수 있습니다. 제공자는 실패한 전달을 재시도하여 이를 처리합니다. 하지만 엔드포인트가 재시도를 안전하게 처리하도록 설계해야 합니다.
지터를 포함한 지수 백오프
제공자는 일반적으로 증가하는 지연으로 재시도합니다: 1분, 5분, 30분, 2시간 등. 이를 지수 백오프라고 합니다. 일부는 썬더링 허드 문제를 피하기 위해 무작위 지터를 추가합니다.
당신 쪽에서는 제공자의 재시도 일정을 제어할 수 없지만, 다음을 해야 합니다:
- 이벤트를 안전하게 저장한 후 가능한 한 빨리
2xx로 응답합니다(처리가 비동기적이더라도). - 영구적인 실패(예: 잘못된 페이로드)의 경우
4xx를 반환하여 제공자가 재시도를 중단하도록 합니다. - 일시적인 실패(예: 데이터베이스 다운)의 경우
5xx를 반환하여 재시도를 트리거합니다.
멱등성: 안전한 재시도의 핵심
재시도는 동일한 이벤트를 여러 번 받을 수 있음을 의미합니다. 처리는 멱등적이어야 합니다. 즉, 동일한 이벤트를 두 번 처리해도 한 번 처리한 것과 같은 효과를 가져야 합니다.
일반적인 접근 방식: 페이로드에 고유 이벤트 ID를 포함합니다. 처리하기 전에 해당 ID를 이미 처리했는지 확인합니다. 그렇다면 200을 반환하고 건너뜁니다.
async function handleWebhook(event) {
const existing = await db.events.findOne({ id: event.id });
if (existing) {
return { status: 200, body: 'Already processed' };
}
await db.events.insert({ id: event.id, processedAt: new Date() });
await processEvent(event);
return { status: 200, body: 'OK' };
}
이 패턴은 제공자가 이벤트를 세 번 보내더라도 한 번만 작업을 수행하도록 보장합니다.
비교: 폴링 vs 웹훅
| 측면 | 폴링 | 웹훅 |
|---|---|---|
| 지연 시간 | 간격에 따라 다름(예: 30초) | 거의 실시간 |
| 서버 부하 | 이벤트가 없어도 지속적 | 이벤트가 발생할 때만 |
| 복잡성 | 구현이 간단 | 엔드포인트, 검증, 재시도 필요 |
| 안정성 | 재시도를 직접 제어 | 제공자의 재시도 정책에 의존 |
실시간 업데이트에는 웹훅이 우수하지만, 전달 누락이 걱정되는 중요한 이벤트의 경우 폴링을 대체 수단으로 사용할 수 있습니다.
모범 사례 체크리스트
- 항상 서명을 검증하세요. 검증되지 않은 웹훅을 절대 신뢰하지 마세요.
- HTTPS를 사용하세요. 웹훅 페이로드에는 종종 민감한 데이터가 포함됩니다.
- 빠르게 응답하세요. 수신을 신속히 확인하고, 필요한 경우 비동기적으로 처리하세요.
- 멱등성을 구현하세요. 이벤트 ID를 저장하여 중복 처리를 방지하세요.
- 모든 것을 로깅하세요. 디버깅과 감사를 위해 수신된 이벤트 기록을 유지하세요.
- 실패를 모니터링하세요. 반복적인 5xx 응답이나 서명 검증 실패에 대한 알림을 설정하세요.
- 실제 페이로드로 테스트하세요. 제공자의 테스트 모드를 사용하거나 샘플 이벤트를 엔드포인트로 보내세요.
웹훅 문제 디버깅
웹훅이 실패하면 첫 번째 단계는 원시 요청을 검사하는 것입니다. 일반적인 문제:
- 서명 불일치: 원시 본문 대신 파싱된 본문을 사용하고 있습니다. 원시 본문을 캡처하여 수정하세요.
- 타임아웃: 엔드포인트가 응답하는 데 너무 오래 걸립니다. 무거운 처리를 백그라운드 작업으로 이동하세요.
- 누락된 이벤트: 제공자의 대시보드에서 전달 로그와 재시도 기록을 확인하세요.
HTTP 트래픽을 디버깅하는 경우 Nginx Log Analyzer와 같은 도구가 요청 실패 및 응답 코드의 패턴을 발견하는 데 도움이 될 수 있습니다.
FAQ
웹훅 시크릿이란 무엇이며 어떻게 저장하나요?
시크릿은 페이로드에 서명하는 데 사용되는 공유 키입니다. 환경 변수나 시크릿 관리자에 저장하세요. 코드나 버전 관리에는 절대 넣지 마세요. 제공자가 지원하는 경우 주기적으로 교체하세요.
제공자는 실패한 웹훅을 몇 번 재시도하나요?
제공자마다 다릅니다. 일부는 지수 백오프로 24시간 동안 재시도하고, 다른 일부는 3일 동안 재시도합니다. 정확한 일정과 최대 시도 횟수는 제공자의 문서를 확인하세요.
서버리스 함수와 함께 웹훅을 사용할 수 있나요?
네, 하지만 실행 시간 제한에 유의하세요. 웹훅을 빠르게 확인(200 반환)하고 무거운 처리를 큐나 백그라운드 함수로 오프로드하여 타임아웃을 피하세요.
견고한 웹훅 통합을 구축할 준비가 되셨나요? 서명 검증과 멱등성 구현부터 시작하세요. 이 두 단계가 몇 시간의 디버깅을 절약하고 비용이 많이 드는 보안 사고를 예방할 것입니다.