Webhook 전쟁: 결제 연동에서 발생하는 무음 실패와의 전투
Source: Dev.to
당신을 배신하는 설정
제가 깨달은 잔인한 진실: 웹훅은 크게 실패하기보다 조용히 실패하는 경우가 더 많습니다.
반드시 요구되어야 할 두 가지 핵심 우회 방법
당신이 몰랐던 백업 폴러
대부분의 결제 게이트웨이 문서는 이 보석을 작은 글씨에 숨겨 놓습니다:
“정기적인 간격으로 거래 상태를 조회하는 재조회 서비스를 설정하세요.”
이는 제안이 아니라 웹훅 전달이 보장되지 않음을 인정하는 내용입니다. 서버가 다운되었나요? 네트워크가 끊겼나요? 속도 제한에 걸렸나요? 웹훅은 공허 속으로 사라집니다.
내 구현 전략 (Node.js):
// Simple poller service (Node.js example)
async function reconcileTransactions() {
const pending = await getPendingTransactions();
for (const tx of pending) {
const status = await paymentGateway.checkStatus(tx.reference);
if (status.hasChanged()) {
await processWebhookPayload(status);
await markAsReconciled(tx.id);
}
}
}
// Run every 15 minutes
setInterval(reconcileTransactions, 15 * 60 * 1000);
슬래시 뒤에 붙는 문제
제가 겪은 Apache 함정: 웹훅 엔드포인트가 디렉터리일 때(예: /webhook), 슬래시가 없으면 Apache가 자동으로 /webhook/ 로 리다이렉트합니다. 이 과정에서 POST 요청이 GET 으로 변환돼, 웹훅은 빈 요청을 받으면서 200 OK 를 반환합니다.
문서에서 제시하는 우회 방법: “URL에 슬래시를 붙이세요.”
보다 견고한 해결책은 서버 설정을 직접 고치는 것입니다:
# .htaccess – The RIGHT way
RewriteEngine On
RewriteCond %{REQUEST_METHOD} POST
RewriteRule ^webhook$ /webhook/ [L]
또는, 더 간단하게:
DirectorySlash Off
실제로 본 피해 사례
- SaaS 기업: 취소된 구독이 계속 청구돼 월간 반복 매출 $14 K 손실 (웹훅 실패).
- 이커머스 스토어: 결제가 성공했음에도 불구하고 200개 이상의 디지털 상품이 전달되지 않음.
- 예약 플랫폼: 웹훅이 순서대로 도착하지 않아 이중 예약 발생.
내 웹훅 체크리스트
라이브 전
- 멱등성 키: 동일한 웹훅을 여러 번 처리해도 안전하도록.
- 데드 레터 큐: 실패한 웹훅을 수동 검토를 위해 저장.
- 시그니처 검증: 검증 없이 들어오는 요청을 절대 신뢰하지 않기.
- 완전한 로깅: 요청 본문, 헤더, 처리 결과, 타임스탬프 모두 기록.
운영 모니터링
- 성공률 대시보드: 실시간으로 전달 실패를 추적.
- 자동 조정: 게이트웨이와 데이터베이스를 비교하는 일일 체크.
- 알림: 실패율이 1 % 를 초과하면 즉시 통보.
- 수동 트리거: 게이트웨이 대시보드에서 웹훅을 재전송할 수 있는 기능.
사고 방식 전환
웹훅 알림을 “최선 노력” 알림으로 간주하고, 신뢰할 수 있는 트리거로 여기지 마세요. 시스템이 웹훅 실패를 견딜 수 있게 설계해야 합니다. 결제 통합이 카드 하우스가 되어서는 안 됩니다. 슬래시 뒤에 붙이는 문제와 폴링 권고는 더 깊은 아키텍처 문제에 대한 임시 방편에 불과합니다.
여러분 차례
이번 주에 결제 통합을 구현한다면, 한 가지 부탁드립니다: 라이브 전 폴링 서비스를 반드시 추가하세요. 서버가 피크 시간에 재부팅될 때 미래의 여러분이 크게 감사할 겁니다.
계속해서 빌드하고 (웹훅을 정직하게 유지하세요),
Makai