결제 웹훅 안전하게 받기 — 위조·중복·순서·재전송 (토스페이먼츠·포트원)
테스트 결제가 된다고 결제 연동이 끝난 게 아닙니다. 운영 키로 바꾸기 전에 웹훅을 네 가지 경우에 대비해야 합니다. 가짜 웹훅, 두 번 오는 웹훅, 순서가 뒤집혀 오는 웹훅, 그리고 아예 오지 않는 웹훅입니다.
웹훅 본문은 그대로 믿지 말고 서명으로 검증하거나 결제 조회 API로 다시 확인합니다. 결제 키에 유니크 제약을 걸고 상태는 조건부로만 바꿉니다. 10초 안에 200을 돌려주고 무거운 일은 뒤로 미룹니다. 웹훅이 안 와도 끝낼 수 있게 주기적으로 대사합니다.
01위조 — 본문을 그대로 믿지 않기
웹훅 주소는 공개된 URL이라 누구나 요청을 보낼 수 있습니다. "결제 완료" 본문만 보고 주문을 확정하면, 결제 없이 주문을 만드는 요청이 그대로 통과합니다.
- 서명을 주는 결제사: 포트원 V2는 Standard Webhooks 방식의 서명 헤더를 보냅니다. 서버 SDK의 웹훅 검증 함수에 원본 본문 문자열과 헤더, 웹훅 시크릿을 넘겨 검증합니다. JSON으로 파싱했다가 다시 직렬화한 문자열로는 서명이 맞지 않습니다.
- 서명 방식이 문서에 없는 경우: 2026-10 기준 토스페이먼츠 웹훅 문서에는 결제 상태 웹훅의 서명 검증 방식이 적혀 있지 않습니다. 이럴 때는 웹훅을 "상태가 바뀌었다는 신호"로만 쓰고, 서버에서 시크릿 키로 결제 조회 API를 다시 불러 실제 상태를 확인한 뒤 반영합니다.
02중복 — 두 번 와도 한 번만
웹훅은 "최소 한 번" 전달이 기본입니다. 응답이 늦거나 네트워크가 끊기면 같은 알림이 다시 옵니다. 중복을 막지 않으면 주문이 두 번 확정되거나 포인트가 두 번 쌓입니다.
- 결제 키나 이벤트 id에 유니크 제약을 걸어 두 번째 반영이 DB에서 막히게 합니다.
- 상태 변경은
UPDATE … SET status='PAID' WHERE id=? AND status='PENDING'처럼 이전 상태를 조건으로 겁니다. 바뀐 행이 0이면 이미 처리된 것입니다.
03순서 — 취소가 먼저 와도
웹훅은 보낸 순서대로 도착한다는 보장이 없습니다. 승인 알림보다 취소 알림이 먼저 올 수 있습니다. 주문 상태를 상태 머신으로 두고, 이미 더 뒤 단계로 간 주문에는 앞 단계 이벤트를 반영하지 않습니다. 결제 재조회 방식이라면 조회 시점의 최종 상태를 쓰므로 이 문제가 줄어듭니다.
04재전송 — 10초 안에 200
토스페이먼츠 문서 기준으로, 웹훅을 받으면 10초 이내에 200을 돌려줘야 하고, 실패하면 최대 7회 다시 보냅니다. 간격은 1분에서 시작해 점점 길어져 마지막은 4096분 뒤입니다. 그래서 핸들러는 검증과 기록만 하고 먼저 200을 돌려준 뒤, 메일 발송 같은 무거운 일은 큐로 넘기는 게 좋습니다. 처리 중 오류로 5xx를 돌려주면 재전송이 오므로, 02의 중복 방지가 함께 있어야 안전합니다.
05아예 안 올 때 — 주기적 대사
결제는 됐는데 고객이 창을 닫고, 웹훅도 실패하는 경우가 있습니다. "결제 대기"로 오래 남은 주문을 주기적으로 찾아 결제 조회 API로 상태를 맞추는 작업을 둡니다. 이것이 없으면 돈은 나갔는데 주문은 없는 고객이 생깁니다.
출시 전 체크리스트
- 웹훅 본문을 그대로 믿지 않는다 — 서명 검증 또는 결제 재조회
- 결제 키·이벤트 id 유니크 제약, 상태는 조건부 업데이트
- 상태 머신으로 순서 뒤집힘 처리
- 10초 안에 200, 무거운 일은 뒤로
- 결제 대기 주문 주기적 대사
- 테스트 모드에서 같은 웹훅을 두 번 보내 봤다
내 저장소의 결제 연동을 점검받고 싶다면
vamuri는 웹훅 검증, 중복 반영, 환불 규정까지 결제 관련 항목을 포함한 79개 항목을 저장소에서 읽어, 근거(파일·줄)와 함께 정리해 드립니다. 저장소는 읽기만 합니다. 지금은 무료 기간이라 진단이 무료입니다.