쇼핑몰 운영자가 자주 받는 전화는 "카드는 빠져나갔는데 주문 내역이 없다"는 문의입니다. 고객 휴대폰에는 승인 문자가 남아 있고, 관리자 화면에는 그 주문이 없습니다. 결제대행사 콘솔을 열어 보면 승인은 분명히 되어 있습니다. 결국 담당자가 손으로 주문을 만들어 넣습니다.
이 사고는 대개 결제사 서버와 우리 서버 사이를 오가는 알림 한 건이 사라져서 생깁니다. 그 알림을 웹훅이라고 부릅니다. 결제사가 "이 건이 승인됐습니다"라고 가맹점 서버에 직접 신호를 보내는 구조입니다. 문제는 이 신호가 몇 번까지 다시 오는지가 회사마다 다르고, 어떤 곳은 기본값이 한 번이라는 점입니다.
웹훅은 보낸 것과 받은 것이 다른 일입니다
결제창에서 승인이 끝나면 고객 화면은 완료 페이지로 넘어갑니다. 하지만 주문을 확정하는 신호는 고객 브라우저가 아니라 서버 간 알림으로 옵니다. 브라우저는 닫히거나 끊길 수 있어서 기준으로 삼을 수 없기 때문입니다.
그 알림이 도착하지 못하는 이유는 평범합니다. 배포 중 서버 재시작이나 순간적인 응답 지연, 방화벽 규칙 변경이면 충분합니다. 포트원 V2는 연결과 읽기 대기 시간을 모두 30초로 두고 그 안에 응답이 없으면 실패로 처리합니다. 토스페이먼츠는 10초 안에 200 응답을 받아야 성공으로 봅니다.
기본값이 1회 발송인 곳이 있습니다
포트원 V1(구 아임포트) 문서에는 "웹훅은 기본적으로 1회 전송되도록 설정되어 있습니다"라고 적혀 있습니다. 재발송을 쓰려면 고객사 식별코드를 적어 기술지원 메일로 따로 요청해야 합니다. 신청하면 최대 5회까지 1분 간격으로 재시도됩니다.
V1으로 연동했고 재발송을 신청하지 않았다면, 그 결제 알림은 딱 한 번만 옵니다. 그 한 번이 배포 시각과 겹치면 주문은 그대로 비어 있습니다. 개발사가 알아서 켜 주는 설정이 아니고, 코드를 봐도 켜졌는지 알 수 없습니다.
재시도는 결제사가 당연히 해 주는 기능이 아닙니다. 어떤 곳에서는 따로 신청해야 켜지는 옵션입니다.
재시도 횟수와 간격은 중계사마다 다릅니다
포트원 V2는 실패 시 최대 5회 재전송하고, 간격은 0분, 1분, 4분, 16분, 64분, 256분으로 늘어납니다. 여기에 무작위 지연이 더해집니다. 마지막 시도까지 걸리는 시간은 다 더해도 여섯 시간이 되지 않습니다.
토스페이먼츠는 최대 7회이고 간격은 1분에서 4096분까지 네 배씩 늘어납니다. 공식 가이드는 마지막 시도가 최초 전송으로부터 3일 19시간 뒤라고 적고 있습니다. 해외 결제를 함께 쓰는 곳이라면 스트라이프도 보셔야 하는데, 라이브 모드는 최대 3일간 재시도하고 샌드박스는 세 번만 시도합니다.
이 숫자는 "우리 서버가 몇 시간까지 죽어 있어도 복구되는가"를 뜻합니다. 재시도가 전부 소진될 때까지 응답을 못 했다면 그 건은 결제사 쪽에서 종료됩니다.
알림이 비면 돈은 들어오고 주문은 없습니다
결제 승인은 카드사와 결제사 사이에서 이미 끝난 일입니다. 웹훅 전송이 실패해도 승인이 취소되지는 않습니다. 그래서 정산 금액에는 들어 있는데 주문 데이터에는 없는 건이 남습니다.
이 건을 지금 누가 메우는지 보면 답은 하나입니다. 고객이 전화를 걸어 주면 CS가 콘솔에서 승인을 확인하고 주문을 손으로 만듭니다. 고객이 전화하지 않으면 아무도 모릅니다. 유실을 찾아내는 장치가 없는 쇼핑몰에서는 발견이 전부 고객 몫입니다.
받은 알림을 세는 일은 쉽습니다. 어려운 일은 오지 않은 알림을 찾는 일이고, 그것이 세 가지 개발 항목의 목적입니다.
첫째, 중복 수신에도 안전한 멱등 처리를 넣으세요
재시도를 켜면 같은 알림을 두 번 이상 받게 됩니다. 포트원 V1 문서도 서버가 정상 응답했는데 응답이 결제사에 닿지 못하면 재발송될 수 있다고 적고, 같은 웹훅을 여러 번 받아도 문제가 없게 처리하라고 권고합니다.
중복 처리가 없으면 주문이 두 건 생기거나 적립금이 두 번 쌓입니다. 이미 처리한 건인지 먼저 확인하고, 처리했다면 아무 일도 하지 않고 성공 응답만 돌려주는 방식이 기본입니다. 토스페이먼츠는 eventId로 중복을 판별하라고 안내하지만 결제 상태 변경 웹훅에는 그 값이 없어서, 주문번호와 결제키로 기준을 직접 잡아야 합니다.
견적서에는 이렇게 적습니다. "결제 웹훅 수신부에 멱등 처리를 적용하여 동일 건이 2회 이상 수신되어도 주문·재고·적립금 반영이 1회만 일어나도록 구현합니다."
둘째, 재시도와 수동 재전송을 설정으로 확보하세요
계약한 결제사의 재시도가 켜져 있는지 확인하고, 꺼져 있으면 켜는 일 자체가 개발 항목입니다. 포트원 V1은 기술지원 메일로 신청해야 하고, 토스페이먼츠는 콘솔에서 '다시 시도'를 눌러 실패 건이나 전송 중인 건을 다시 보낼 수 있습니다.
수동 재전송에는 기한이 있습니다. 토스페이먼츠 콘솔은 전송 기록을 최근 30일치만 화면에 보여 줍니다. 스트라이프는 대시보드에서 이벤트 생성 후 15일까지, CLI로는 30일까지 재전송됩니다. 한 달이 지나 발견한 건은 버튼이 아니라 사람 손으로 복구해야 합니다.
견적서 문장은 이렇습니다. "결제사 콘솔의 웹훅 재시도 옵션을 활성화하고, 수신 실패 건을 운영자가 직접 재전송할 수 있도록 권한과 절차를 문서로 인계합니다."
셋째, 하루 1회 결제 내역 대조 배치를 넣으세요
재시도가 모두 실패한 건은 결제사 기록에만 남습니다. 그래서 하루 한 번 결제사 조회 API로 승인 목록을 받아 우리 주문과 맞춰 보는 배치가 마지막 그물입니다. 이 배치가 있으면 고객 전화 없이도 유실 건이 드러납니다.
대조 결과는 세 가지로 나뉩니다. 승인은 있는데 주문이 없는 건, 주문은 있는데 승인이 없는 건, 금액이 다른 건입니다. 여기서 중요한 것은 알림입니다. 불일치를 담당자에게 보내지 않으면 배치는 로그만 쌓는 작업이 됩니다.
견적서 문장은 이렇습니다. "매일 1회 결제사 거래내역과 자사 주문 데이터를 대조하는 배치를 구축하고, 불일치 건은 유형별로 분류하여 담당자에게 자동 통보합니다."
견적서에 없는 항목은 결국 운영자 몫이 됩니다
웹훅 수신은 '결제 연동' 한 줄에 묻히기 쉽습니다. 그러면 멱등 처리와 대조 배치는 범위에 없는데 포함된 줄 알게 됩니다. 사고가 난 뒤에 요청하면 추가 개발이고, 그때까지 생긴 유실은 전부 사람이 메웁니다.
그래서 세 줄을 각각 분리해 적는 것이 핵심입니다. '결제 연동'과 '웹훅 유실 대응'은 다른 항목입니다. 금액이 크든 작든, 범위에 적혀 있는지가 나중에 누가 복구하는지를 결정합니다.
이럴 때는 세 항목을 다 넣지 않아도 됩니다
하루 결제가 몇 건인 서비스라면 대조 배치는 과합니다. 결제사 콘솔을 하루 한 번 눈으로 보는 편이 빠르고 싸게 끝납니다. 다만 멱등 처리는 건수와 상관없이 빼면 안 됩니다. 재시도를 켠 순간 중복 수신은 사고가 아니라 설정의 결과이기 때문입니다.
대조 배치도 만능이 아닙니다. 하루 한 번이면 발견까지 최대 24시간이 걸리고, 당일 발송 상품에는 그 지연이 그대로 손해가 됩니다. 조회 API의 호출 제한과 조회 가능 기간은 결제사마다 달라서, 계약한 회사의 문서로 먼저 확인해야 합니다.
계약 전에 이 세 가지를 물어보세요
- 우리가 쓰는 결제사의 웹훅 재시도가 켜져 있습니까? 최대 몇 회입니까?
- 같은 알림이 두 번 와도 주문이 하나만 생기는 것을 어떤 값으로 판별합니까?
- 승인은 있고 주문이 없는 건을 누가, 언제, 어떤 방법으로 발견합니까?
세 질문에 "확인해 보겠습니다"가 돌아오면 그 항목은 아직 견적에 없는 것입니다. 숫자와 담당자 이름으로 답이 나오면 이미 설계에 들어가 있다는 뜻입니다.
재시도 정책은 바뀌므로 숫자는 직접 확인하시는 편이 안전합니다. 토스페이먼츠 웹훅 가이드와 포트원 V1 웹훅 문서에 횟수와 간격이 공개되어 있습니다. 비슷한 운영 사고 기록은 개발 노트에 모아 두었고, 결제 연동 점검이 필요하시면 Codeforest로 문의하세요.
결론: 유실은 막을 수 없으니 발견을 설계하세요
웹훅은 네트워크를 타고 오는 알림이라 언젠가는 빠집니다. 포트원 V1은 기본 1회, V2는 최대 5회, 토스페이먼츠는 최대 7회에서 재시도가 끝납니다. 그 뒤에 남는 건은 결제사가 아니라 우리 쪽에서 찾아야 합니다.
그래서 견적서에 세 줄이 필요합니다. 중복 수신에도 안전한 멱등 처리, 재시도와 수동 재전송 설정, 하루 1회 대조 배치와 불일치 알림입니다. 이 세 줄이 있으면 유실 건은 하루 안에 담당자가 발견합니다. 없으면 고객 전화가 올 때까지 아무도 모릅니다.