웹훅 완벽 가이드: 서명된 페이로드와 재시도
결제 제공업체의 웹훅을 방금 통합했습니다. 문서에는 "결제가 성공하면 귀하의 엔드포인트로 POST 요청을 보냅니다"라고 나와 있습니다. 간단하죠? 그런데 중복 이벤트, 누락된 알림, 그리고 위조 요청에 대한 보안 경고를 발견하게 됩니다. 웹훅은 개념적으로는 간단하지만 실제 운영 환경에서는 까다롭습니다. 이 글에서는 서명된 페이로드와 재시도를 사용하여 웹훅을 올바르게 구현하는 방법을 설명하여 안정적이고 안전한 통합을 구축할 수 있도록 도와드립니다.
웹훅이란 무엇인가?
웹훅은 역방향 API입니다. 애플리케이션이 서비스에 업데이트를 폴링하는 대신, 서비스가 HTTP POST를 통해 이벤트를 엔드포인트로 푸시합니다. 효율적이고 거의 실시간으로 작동합니다. 일반적인 용도로는 결제 알림, CI/CD 트리거, 채팅 통합 등이 있습니다.
하지만 웹훅은 과제를 안고 있습니다. 요청이 실제로 제공업체에서 온 것인지 어떻게 알 수 있을까요? 서버가 다운되면 어떻게 될까요? 동일한 이벤트가 두 번 도착하면 어떻게 될까요? 하나씩 해결해 보겠습니다.
서명된 페이로드로 웹훅 보안 강화
누구나 엔드포인트로 HTTP 요청을 보낼 수 있습니다. 검증 없이는 공격자가 이벤트를 위조하여 사기나 데이터 손상을 초래할 수 있습니다. 표준 해결책은 공유 비밀 키로 페이로드에 서명하는 것입니다.
HMAC 서명 작동 방식
제공업체와 귀하는 비밀 키를 공유합니다. 각 이벤트에 대해 제공업체는 해당 비밀 키를 사용하여 요청 본문의 HMAC(해시 기반 메시지 인증 코드)을 계산하고 서명을 헤더(예: X-Signature)에 담아 보냅니다. 귀하는 자신의 쪽에서 HMAC을 다시 계산하여 비교합니다. 일치하면 요청이 진본이며 변조되지 않았음을 의미합니다.
다음은 Flask를 사용한 Python의 최소 예제입니다:
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b'your_shared_secret'
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Signature')
if not signature:
abort(400, 'Missing signature')
body = request.get_data()
expected = hmac.new(SECRET, body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401, 'Invalid signature')
# Process the event
return '', 200
타이밍 공격을 방지하려면 항상 상수 시간 비교를 위해 hmac.compare_digest를 사용하세요. 또한 불필요한 처리를 피하기 위해 JSON 본문을 파싱하기 전에 서명을 확인하세요.
서명 모범 사례
- 강력한 비밀 키 사용: 최소 32바이트의 무작위 키를 생성하고 안전하게 저장하세요(예: 환경 변수).
- 타임스탬프 포함: 재전송 공격을 방지하기 위해 제공업체는 종종 서명된 페이로드에 타임스탬프를 포함합니다. 몇 분 이상 지난 요청은 거부하세요.
- 주기적으로 비밀 키 교체: 교체 중 다운타임을 피하기 위해 여러 활성 비밀 키를 지원하세요.
- 서명 알고리즘 문서화: 해시 함수(SHA-256이 표준)와 헤더 이름을 명시하세요.
재시도로 실패 처리
제공업체 입장에서 웹훅은 발송 후 망각 방식이지만, 귀하의 엔드포인트가 일시적으로 사용 불가능할 수 있습니다. 전달을 보장하기 위해 제공업체는 재시도 정책을 구현합니다. 수신자로서 귀하는 재시도와 중복에 대비해야 합니다.
제공업체가 사용하는 재시도 전략
대부분의 제공업체는 지수 백오프를 사용하여 재시도합니다. 예: 즉시, 1분 후, 5분 후, 30분 후 등 최대 시도 횟수(보통 5~10회)까지. 일부는 재시도 일정을 구성할 수 있도록 허용하기도 합니다. 정확한 동작은 다양하므로 제공업체의 문서를 읽어보세요.
재시도를 위한 엔드포인트 설계
엔드포인트는 다음을 충족해야 합니다:
- 빠르게 응답: 몇 초 이내에 2xx 상태로 수신 확인을 보내세요. 무거운 처리는 비동기적으로 수행하세요(예: 이벤트를 큐에 넣기).
- 멱등성 유지: 동일한 이벤트가 여러 번 전달될 수 있습니다. 멱등성 키(종종 이벤트 페이로드나 헤더에 제공됨)를 사용하여 중복을 제거하세요. 처리된 이벤트 ID를 합리적인 기간 동안 저장하세요.
- 적절한 상태 코드 반환: 성공 시 2xx, 클라이언트 오류 시 4xx(제공업체가 재시도하지 않을 수 있음), 서버 오류 시 5xx(제공업체가 재시도함).
Python에서 멱등성 검사 예제:
processed_events = set() # In production, use a database or cache
@app.route('/webhook', methods=['POST'])
def webhook():
# ... signature verification ...
event = request.json
event_id = event['id']
if event_id in processed_events:
return '', 200 # Already processed
# Process event
processed_events.add(event_id)
return '', 200
웹훅 구현 테스트
실제 운영에 들어가기 전에 엔드포인트를 철저히 테스트하세요:
- 서명된 요청 시뮬레이션: 유효한 서명과 유효하지 않은 서명을 생성하는 단위 테스트를 작성하세요.
- 재시도 시나리오 테스트: 실패를 시뮬레이션하고 엔드포인트가 중복을 처리하는지 확인하세요.
- 제공업체 도구 사용: 많은 제공업체가 테스트 이벤트를 보낼 수 있는 CLI 또는 대시보드를 제공합니다.
- 모니터링 및 로깅: 수신 이벤트를 기록하고(민감한 데이터 제외) 서명 실패나 높은 오류율에 대한 알림을 설정하세요.
비교: 폴링 vs 웹훅
| 측면 | 폴링 | 웹훅 |
|---|---|---|
| 지연 시간 | 간격에 따라 다름(예: 분) | 거의 실시간 |
| 서버 부하 | 높음(빈번한 요청) | 낮음(이벤트 발생 시에만) |
| 복잡성 | 구현 간단 | 서명 검증 및 재시도 처리 필요 |
| 신뢰성 | 재시도 제어 가능 | 제공업체가 재시도 제어, 중복 처리 필요 |
FAQ
웹훅 서명을 어떻게 검증하나요?
공유 비밀 키를 사용하여 원시 요청 본문의 HMAC을 계산하고 상수 시간 비교 함수를 사용하여 서명 헤더와 비교하세요. 페이로드를 처리하기 전에 항상 검증하세요.
웹훅을 확인하려면 엔드포인트가 무엇을 반환해야 하나요?
이벤트를 수신하고 처리를 위해 큐에 넣는 즉시 2xx 상태 코드(예: 200 OK)를 반환하세요. 시간 초과를 방지하려면 요청 핸들러에서 긴 처리를 피하세요.
중복 웹훅 전달을 어떻게 처리할 수 있나요?
이벤트 페이로드의 멱등성 키(또는 고유 이벤트 ID)를 사용하여 처리된 이벤트를 추적하세요. 이러한 ID를 데이터베이스나 캐시에 저장하고 ID가 이전에 본 적이 있으면 처리를 건너뛰세요.
웹훅 페이로드를 테스트할 준비가 되셨나요? JSON Formatter를 사용하여 수신 웹훅의 JSON 본문을 예쁘게 인쇄하고 유효성을 검사하여 디버깅을 더 쉽게 하세요.