웹훅 수신 설계: 서명 검증·중복 처리·재시도
이벤트 ID와 처리 상태를 저장해 재전송에도 부작용을 제어하는 설계 예제입니다.
웹훅은 같은 이벤트가 다시 오거나 순서가 바뀔 수 있는 외부 입력으로 다루어야 합니다. 수신 HTTP 요청을 한 번 처리했다는 사실과 결제 반영·메일 발송 같은 부작용이 한 번 발생했다는 사실은 다릅니다.
본문을 사용하기 전에 서명 검증
제공자가 요구하는 서명 검증 방식과 원본 바이트 처리 규칙을 따릅니다. JSON을 파싱한 뒤 다시 직렬화하면 공백이나 순서가 바뀌어 검증 대상이 달라질 수 있습니다. 서명뿐 아니라 지원되는 경우 타임스탬프와 재전송 허용 범위도 확인합니다.
서명 헤더와 비밀값을 로그에 남기지 않습니다. 검증 실패 요청은 비즈니스 처리 단계로 보내지 않고, 본문 크기와 입력 스키마에도 제한을 둡니다. 아래 상태 모델은 특정 공급자의 API 구현이 아니라 설계 예제입니다.
이벤트 ID와 처리 상태 저장
(provider, event_id) ← 고유 제약
status ← received / processing / completed / failed
received_at
attempts
last_error_code ← 비밀값을 제외한 오류 분류
같은 이벤트를 동시에 받았을 때도 하나만 등록되도록 DB 고유 제약 같은 원자적 수단을 사용합니다. 메모리의 ‘처리한 ID 집합’만으로는 재시작이나 여러 서버에서 중복을 막기 어렵습니다.
접수 성공과 작업 완료 구분하기
서명과 형식을 확인한 뒤 이벤트를 내구성 있는 저장소에 접수하고 빠르게 응답하는 구성을 검토할 수 있습니다. 이후 작업자가 처리합니다. 저장 전에 성공 응답을 보내면 프로세스가 죽었을 때 이벤트가 사라질 수 있습니다.
DB 변경과 처리 완료 상태를 한 트랜잭션으로 묶을 수 있는 경우와 외부 API 호출이 섞인 경우는 다릅니다. 외부 호출 성공 직후 프로세스가 죽으면 로컬에는 완료 기록이 없을 수 있습니다. 외부 공급자의 멱등성 키나 상태 조회로 재시도 안전성을 확보해야 합니다.
순서와 실패를 시험하기
같은 이벤트 두 번, 이전 이벤트가 나중에 도착, 작업 중 종료, 외부 응답 시간 초과를 각각 재현합니다. 이벤트 도착 순서를 최종 상태의 근거로 삼지 말고 원본의 버전이나 상태를 확인하는 방식을 검토합니다.
재시도 간격과 최대 횟수, 처리되지 않은 이벤트의 보관·재처리 절차도 정합니다. ‘정확히 한 번’이라는 표현을 사용하려면 어떤 저장소와 어떤 부작용 범위에서 보장하는지 명시해야 합니다. 시스템 전체의 막연한 보장으로 사용하지 않습니다.