← 기술 지식

기술 지식 / 보안 / 파일·외부 연동

Webhook 서명 검증과 내구 접수·중복 처리

원문 서명·시각 검증을 내구 inbox와 연결하고 중복 이벤트·역순 상태·빠른 ACK·키 회전의 별도 책임을 설명합니다.

보안 · 원리·구현·운영
기술 지식 트리

이 노트는 webhook 수신을 서명 검증 한 번으로 끝나는 인증 문제가 아니라, 원문 바이트 검증, 재생·중복 판정, 내구 inbox commit, 순서가 있는 상태 변경이 이어지는 처리 파이프라인으로 봅니다. HTTP 2xx의 의미도 업무 처리가 끝났다는 뜻이 아니라 안전하게 다시 처리할 수 있는 접수 경계로 제한합니다.

진위가 확인된 이벤트의 중복 전달

결제사가 승인 이벤트 E를 보냈고 우리 서버가 DB에 반영했지만 HTTP 응답이 유실됐다고 합시다. 결제사는 E를 다시 보낼 수 있습니다. 두 전달 모두 유효한 서명을 가질 수 있으므로 서명 검증만으로 중복 효과가 없어지지 않습니다.

서명은 지정된 키로 발급된 메시지의 무결성과 출처 근거를 확인합니다. 재생 허용 창, 논리 이벤트 중복, 결제 상태 순서, 원장 변경 권한은 그 뒤의 별도 검사입니다.

서명 검증의 선행 조건은 전달자가 지정 키로 해당 원문을 만들었다는 사실이고, 후속 처리의 조건은 이 이벤트가 아직 효과로 반영되지 않았다는 사실입니다. HTTP 응답 유실은 provider가 같은 event ID를 다시 보내는 정상 복구 경로를 만들 수 있으므로, 유효한 서명이 붙은 동일 이벤트가 여러 번 도착하는 상황을 기본 입력으로 다룹니다.

서명 입력 바이트와 파싱 JSON의 구분

제공자가 서명한 원문 body와 지정 헤더를 사용합니다. JSON을 파싱한 뒤 다시 직렬화하면 키 순서·공백·숫자 표현이 달라져 서명 입력이 바뀔 수 있습니다. 웹 프레임워크의 body middleware가 원문을 보존하는지 확인하고, 읽기 전부터 바이트·시간 상한을 둡니다.

제공자에 따라 서명 입력은 timestamp와 body의 결합 등으로 다릅니다. 직접 추측한 조합이 아니라 공식 verifier를 사용하고 허용 알고리즘·키·버전을 설정합니다. 비교는 검증된 안전한 API를 사용합니다. timestamp가 서명에 묶였는지 확인하지 않고 헤더 시각만 검사하면 공격자가 시각을 바꿀 수 있습니다.

검사 막는 문제 남는 문제
서명·키 검증 변조·미신뢰 발급 같은 진짜 이벤트 중복
서명된 시각·허용 오차 오래된 전달의 재생 창 허용 창 안 재전달
이벤트 ID 유일성 동일 논리 이벤트의 중복 효과 서로 다른 이벤트의 역순
상태·버전·원장 규칙 잘못된 상태 전이 외부 시스템과의 최종 대사

시각 검사는 제공자의 재시도 계약을 고려해야 합니다. 원래 이벤트 발생 시각과 매 전달의 서명 시각이 다를 수 있습니다. 정상 지연·시계 오차를 이유로 무제한 재생을 허용하지도, 짧은 고정 창으로 모든 정상 재전달을 거절하지도 않습니다.

수신 흐름은 크기·시간 제한을 먼저 적용하고, framework가 보존한 원문 bytes와 지정 헤더를 verifier에 전달한 뒤, 검증 성공 후에만 JSON 의미를 읽는 순서여야 합니다. JSON parse 후 재직렬화한 결과를 서명 입력으로 사용하면 공백·키 순서·숫자 표기가 달라질 수 있으므로, 원문 보존 여부를 middleware 테스트로 확인합니다.

내구 접수 이후의 빠른 2xx 반환

인증된 전달을 먼저 내구 기록으로 바꿉니다원문 서명·시간 검증. 내구 inbox에 이벤트 저장: 제공자·계정·event ID. 접수 2xx 반환. worker의 멱등 상태 변경검증된 payload접수 커밋 확인독립 재처리 가능원문 서명·시간 검증내구 inbox에 이벤트 저장제공자·계정·event ID접수 2xx 반환worker의 멱등 상태 변경
인증된 전달을 먼저 내구 기록으로 바꿉니다 화살표는 접수와 후속 처리 순서입니다. 2xx는 inbox 커밋 뒤 반환하며, worker 실패는 별도 재처리로 복구합니다.
그림을 글로 읽기
  • 원문 서명·시간 검증
  • 내구 inbox에 이벤트 저장: 제공자·계정·event ID
  • 접수 2xx 반환
  • worker의 멱등 상태 변경
  1. 원문 서명·시간 검증 → 내구 inbox에 이벤트 저장: 검증된 payload
  2. 내구 inbox에 이벤트 저장 → 접수 2xx 반환: 접수 커밋 확인
  3. 접수 2xx 반환 → worker의 멱등 상태 변경: 독립 재처리 가능

여기서 inbox는 검증된 이벤트를 worker와 분리해 다시 읽을 수 있도록 먼저 저장하는 내구 접수 기록입니다. 그림은 설명용 순서라 worker가 ACK 네트워크 전달보다 먼저 실행될 수도 있습니다. 핵심은 inbox 커밋이 끝난 뒤에만 2xx(HTTP 성공 응답)를 반환하고, worker 실행은 실패해도 재개할 수 있어야 한다는 점입니다. 메모리 큐에만 넣고 바로 2xx를 반환하면 프로세스가 중단될 때 이벤트를 잃습니다.

inbox의 중복 키는 제공자·계정 등 event ID의 실제 유일 범위를 포함합니다. 같은 ID에 다른 payload가 오면 조용히 성공 처리하지 않고 해시·저장 원문과 비교해 충돌을 조사합니다. 서명 검증을 생략한 중복 경로가 민감 정보를 응답으로 드러내지 않게 합니다.

상태 추적은 received bytes → verified → inbox committed → 2xx → worker effect로 나눕니다. inbox commit 전에 2xx를 반환하면 process loss에서 이벤트가 사라지고, commit 후 worker가 실패하면 2xx는 유지되지만 재처리 대상이 됩니다. 같은 event ID에 다른 payload가 오면 중복 성공으로 숨기지 않고 저장 hash와 원문을 비교해 충돌로 분류합니다.

처리 ID와 원장 변경의 단일 거래

worker가 이벤트 ID만 먼저 처리 완료로 저장하고 원장 쓰기 전에 죽으면 재시도가 효과를 건너뜁니다. 반대로 원장을 먼저 쓰고 ID를 나중에 기록하면 같은 효과가 중복 반영됩니다. 지원되는 한 DB 거래 안에서 처리 마커·현재 상태 검사·원장 변경을 함께 커밋하고, 다른 서비스로 보낼 이벤트는 outbox(후속 전달용 기록)로 연결합니다.

취소 이벤트가 승인보다 먼저 도착했다고 단순 도착 순서대로 최종 상태를 덮지 않습니다. 제공자가 보장하는 버전·전이 규칙을 확인하고 필요하면 결제사의 권위 상태를 조회해 대사합니다. 이벤트 시각만으로 모든 분산 순서를 정할 수 있다고 가정하지 않습니다. payload의 사용자 ID나 URL도 인증된 메시지라는 이유로 무조건 인가·외부 조회 근거로 쓰지 않습니다.

처리 마커를 먼저 쓰는 경우와 원장을 먼저 쓰는 경우를 각각 중단시켜 보면 한쪽은 유실, 다른 쪽은 중복이라는 반례가 나옵니다. 지원되는 DB 거래에서 marker·상태 검사·원장 변경을 함께 확정하고, 외부 전달은 outbox로 분리해야 worker 재시도가 같은 effect key를 만났을 때 결과를 재사용할 수 있습니다.

키 교체와 처리 실패의 운영 신호 분리

구·신 서명키 중첩 기간은 제공자 계약과 토큰·전달 수명에 맞춥니다. 모르는 키를 받았다고 임의 URL에서 비밀을 읽거나 서명 검증을 끄지 않습니다. 서명 거절률, inbox 지연, worker 재시도, 중복률, 대사 불일치를 따로 관찰합니다.

테스트 제공자에서 정상 서명·원문 공백 변경·오래된 전달·같은 E 반복·취소 선행·inbox 커밋 뒤 중단을 재현합니다. 수신 HTTP 성공률만 아니라 최종 원장 효과가 한 번이며 접수한 이벤트가 실패 상태에서도 추적 가능한지 확인합니다. 이 노트는 학습용 접수 설계이며 실제 결제 webhook을 처리한 기록은 아닙니다.

운영 선택은 구·신 키의 중첩 기간, provider retry 창, verifier의 unknown key 처리, inbox 지연·worker 재시도를 별도 정책으로 두는 것입니다. 테스트 판정은 정상 서명, 원문 1바이트 변경, 허용 창 밖 timestamp, 같은 ID 반복, 역순 상태, inbox commit 뒤 중단에서 각각 기대 오류와 최종 원장 효과를 확인하는 방식으로 작성합니다.

후속·관련 개념

이 개념의 확장

함께 읽을 문서

전체 기술 지식
선택 연습 · 관련 질문 1개

질문은 이해를 확인하는 보조 자료입니다. 학습 경로와 문서의 범위는 질문 수와 독립적입니다.