블로그
열린 우편함을 향해 날아가는 색색의 종이 봉투.

2026년 8월 31일4분 분량Администратор

웹훅이 두 번 왔다. 그래도 주문은 한 번만 처리되어야 하는 이유

결제 이벤트 수신의 실무 설계: 진위 검증, 중복 방지, 상태 정합성 확보, 누락된 거래의 복구까지.

HTTP 200은 이미 보냈는데 잔액에는 입금이 없습니다. 로그에는 '성공'이라고 적혀 있고, 고객은 상품을 기다리며, PG사는 더 이상 즉시 재전송할 의무가 없습니다. 이런 고장은 웹훅 중복이나 누락처럼 두 사건의 경계에서 생깁니다. 메시지는 받았지만 비즈니스 작업은 끝까지 수행하지 못한 것입니다. 웹훅은 열 줄짜리 핸들러 작성이 아니라 바로 이 경계에서부터 살펴보는 편이 유용합니다.

어떤 시스템을 신뢰할지 먼저 정하라

결제 연동에는 서로 다른 질문이 여러 개 있습니다. PG사는 자기 쪽 거래의 상태를 알려 줍니다. 내부 원장은 플랫폼이 이미 어떤 작업을 실행했는지 보여 줍니다. 정산 리포트는 실제 자금 이동을 확인하는 데 도움이 됩니다. 질문마다 기준이 되는 출처를 정하십시오. 결제 상태는 PG사가 제공하는 메커니즘으로 확인하고, 출고는 내부 기록으로 확인하며, 정산 입금은 대사로 확인합니다. 올바른 결제 알림이라도 이 세 사건 모두에 대한 만능 증거로 삼아서는 안 됩니다.

어댑터마다 짧은 계약을 정리하십시오. 어떤 식별자가 안정적인지, 어떤 이벤트가 반복될 수 있는지, 최신 상태는 어디서 볼 수 있는지, 어떤 변경이 허용되는지입니다. 예를 들어 Stripe는 중복 전송이 있을 수 있고 이벤트 순서를 보장하지 않는다고 밝힙니다. 시간순 정렬만으로는 상태 전이가 허용되는지 증명할 수 없습니다. 이런 세부 사항이 모든 PG사에 똑같이 적용되는 그럴듯한 공통 설계보다 중요합니다.

이벤트 수신과 작업 실행을 분리하라

제안하는 설계는 해당 API의 규칙에 따른 진위 검증에서 시작합니다. 그다음 이벤트를 수신 로그에 기록하고, 비즈니스 처리는 따로 실행합니다. 수신 확인은 메시지를 안정적으로 저장한 뒤, 계약에서 정한 시간 안에 보내십시오. 기록에 실패했다면 시스템은 이벤트를 이미 받은 척해서는 안 됩니다. 오래 걸리는 출고, 구매자 이메일, 리포트 갱신은 이 짧은 경로에 넣지 않는 것이 좋습니다.

진위 검증 방식도 제공사마다 다릅니다. PayPal은 알림 서명 검증 방법을 설명합니다. 직접 검증할 때는 요청 본문의 원본 바이트가 중요합니다. 그러므로 애플리케이션의 중간 처리 계층이 검증 전에 원본 데이터를 바꾸지 않는지 미리 확인하십시오. 해당 문서를 읽지 않은 채 한 서비스의 요구 사항을 다른 서비스로 옮기지 마십시오.

수신 로그에는 PG사, 계정 정보, 이벤트 식별자, 거래 식별자, 수신 시각, 검증 결과, 처리 상태를 저장해 두면 유용합니다. 전체 본문은 타당한 필요가 있을 때만, 접근 제한과 보관 기간을 정해 저장하십시오. 고객 지원팀에 필요한 것은 보통 링크와 상태이지, 메시지마다 담긴 개인정보의 사본이 아닙니다.

전송의 반복과 비즈니스 작업의 반복은 다르다

이벤트 식별자 확인은 같은 전송의 반복을 막아 줍니다. 여기에 더해 작업 자체도 보호하십시오. '결제 거래 + 입금' 쌍에 대해 허용된 실행 기록은 하나만 존재해야 합니다. 서로 다른 메시지가 같은 비즈니스 결과를 설명할 수 있기 때문에 이는 별도의 보호 계층입니다. 실행된 작업의 기록과 내부 잔액 변경은 하나의 일관된 트랜잭션으로 설계하십시오. 그렇지 않으면 둘 사이에서 장애가 나면 중복 입금의 여지가 남습니다.

중복 제거 키를 아무렇게나 고르지 마십시오. Adyen은 eventCode와 pspReference가 같은 반복 전송을 설명합니다. 다른 필드는 다를 수 있는데도 말입니다. 즉, JSON 전체에 대한 범용 해시가 특정 형식의 규칙을 대신하지 못합니다. 반대로 키가 너무 넓으면 정당한 후속 이벤트, 예를 들어 별도의 부분 환불을 가려 버릴 수 있습니다.

결제 하나를 끝까지 따라가 보라

가상의 예에서 주문 M-418의 금액은 72유로입니다. 결제 확인 이벤트가 먼저 저장됩니다. 워크플로가 주문을 찾고, 금액, 통화, 계정을 대조하고, 결제를 갱신한 뒤 출고 작업을 하나 만듭니다. 늦게 도착한 처리 중 알림은 이력에 남지만 주문 상태를 되돌리지 않습니다. 두 번째 결제 확인은 출고를 하나 더 만들지 않습니다. 환불 이벤트는 주문이 이미 결제되었다는 이유로 버려지지 않고, 자체 식별자와 규칙에 따라 처리됩니다.

메시지끼리 서로 모순된다면 해당 거래를 검토 대상으로 돌리고, 허용된 방법으로 최신 데이터를 조회하십시오. 적절한 온라인 조회가 없는 결제수단이라면 정해진 리포트와 PG사 에스컬레이션을 이용합니다. 새 알림이 오지 않는다는 사실만으로 거절이 증명되지는 않습니다.

빨간 대기열에는 출구가 있어야 한다

별도의 오류 대기열은 이벤트를 안전하게 다시 작업으로 돌려보낼 수 있는 동안에만 유용합니다. 제안하는 프로세스에서 각 기록에는 중단 사유가 담깁니다. 알 수 없는 주문, 금액 충돌, 지원하지 않는 유형, 기술 오류입니다. 앞의 세 가지는 판단이 필요하고, 마지막은 재시도로 해결될 수 있습니다. '전부 다시 실행' 버튼 하나는 이 차이를 지워 버리고, 복구를 새로운 장애로 바꿉니다.

재처리 전에 운영자에게 이미 실행된 작업을 보여 주십시오. 입금, 출고, 환불, 고객 알림입니다. 반복해야 하는 것은 빠진, 허용된 단계입니다. 파서를 고친 뒤 비즈니스 시나리오 전체를 처음부터 다시 돌리면, 이벤트 수신이 멱등하더라도 이메일 발송이나 배송 생성까지 멱등하다는 보장은 없습니다. 부수 작업은 코드 업데이트 이후를 포함해 따로 테스트하십시오.

보기 좋은 비율보다 경과 시간이 중요하다

가상의 대기열에 최신 이벤트 900건과 어제부터 멈춰 있는 메시지 1건이 있다고 합시다. 구매자 한 명이 결과를 받지 못한 상태인데도 평균 처리 시간은 괜찮아 보일 수 있습니다. 거래 유형별로 가장 오래된 미처리 이벤트의 경과 시간과 담당자 없는 이벤트 수를 지켜보십시오. 환불, 상품 출고, 안내성 알림은 허용 가능한 지연이 서로 다를 수 있습니다.

복구를 점검하려면 특정 이벤트 하나를 골라 최초 수신부터 금전적 효과까지 따라가 보십시오. 결과뿐 아니라 핸들러 버전도 기록하십시오. 데이터 형식이 바뀌었을 때 어떤 과거 이벤트를 새 코드로 재생할 수 있고, 어떤 이벤트에는 호환 파싱이 필요한지 알 수 있습니다. 가상의 디버깅을 위해 전체 payload를 무기한 보관하지 마십시오. 타당한 정보 세트와 승인된 보관 기간이면 충분합니다.

오류 대기열에서 어제 이벤트 하나를 골라 보십시오. 직원이 빠진 작업만 안전하게 마무리하고, 나머지가 왜 반복되지 않는지 보여 줄 수 있다면 복구 경로는 실제로 존재하는 것입니다. 주문 상태를 손으로 바꿔야 한다면, 웹훅 핸들러는 아직 일을 시스템의 추측에 맡기고 있는 것입니다.

출처

  1. 1.docs.stripe.com/webhooks
  2. 2.docs.adyen.com/development-resources/webhooks/handle-webhook-events
  3. 3.developer.paypal.com/api/rest/webhooks/rest

댓글 · 0

아직 댓글이 없습니다 — 첫 댓글을 남겨 보세요.

로그인 · 댓글을 남기려면 로그인하세요.