웹훅과 권한 동기화
Paddle 이벤트 저장, 멱등성, 상태 전이, 한국 결제수단 타입 변경 대응
핵심 요약
- 제품 권한은 Checkout 성공이 아니라 서버에서 검증된 webhook 이벤트로만 열어야 합니다.
- 최소 구독 이벤트는 transaction.created/updated와 subscription.created/updated 네 가지입니다.
notification_id로 중복을 막고occurred_at으로 순서를 보정해 멱등성을 보장합니다.- 권한은 이벤트마다 if문을 두지 말고 현재 subscription snapshot으로 재계산합니다(active/trialing→full, past_due→grace).
- 한국 결제수단은
south_korea_local_card·kakao_pay등 신규 타입을 쓰되 과거korea_local값도 유지합니다.
Paddle 연동의 안정성은 Checkout 버튼이 아니라 webhook 처리에서 갈립니다. 고객은 결제창을 닫고, 카드 인증은 지연되고, webhook은 순서대로 오지 않습니다. 제품 권한은 반드시 서버에서 검증한 이벤트로만 엽니다.
최소 이벤트
Paddle 공식 문서는 기본 integration에서 transaction과 subscription 이벤트를 함께 구독하라고 권장합니다.
| 이벤트 | 목적 |
|---|---|
transaction.created | 결제 시도 생성 |
transaction.updated | 결제 상태 변경, totals, payment method 반영 |
subscription.created | subscription ID 연결 |
subscription.updated | status, items, next billing date 반영 |
운영을 더 촘촘히 하려면 renewal, cancellation, pause, payment failed 같은 세부 이벤트를 추가합니다.
저장해야 할 필드
| 필드 | 이유 |
|---|---|
notification_id | webhook 전달 단위 중복 처리 방지 |
event_id | Paddle 이벤트 원본 추적과 재조회 기준 |
occurred_at | 이벤트 순서 보정 |
paddle_customer_id | 고객 매핑 |
paddle_subscription_id | 구독 조회와 상태 동기화 |
paddle_transaction_id | 결제/환불/대사 연결 |
subscription.status | 권한 상태 결정 |
subscription.items[].price.id | 플랜/권한 매핑 |
transaction.details.totals | 마지막 결제 금액 표시 |
payments[].method_details.type | 결제수단 분석과 CS |
처리 흐름
멱등성 원칙
1. webhook signature 검증
2. notification_id 중복 확인
3. 원본 payload 저장
4. occurred_at 기준으로 최신 이벤트인지 확인
5. 내부 billing_state 갱신
6. entitlement 재계산
7. 후속 작업 큐 발행이벤트 핸들러에서 이메일, 권한, CRM, 회계 시스템을 곧바로 다 호출하면 안 됩니다. 원본 이벤트를 저장하고
내부 상태를 갱신한 뒤, 후속 작업은 큐나 job으로 떼어 냅니다. Paddle은 webhook에 Paddle-Signature
헤더를 붙이고, webhook 서버는 200을 빠르게 돌려줘야 합니다. 그래서 raw body 검증과 비동기 처리를 기본값으로
둡니다.
권한 재계산 함수
권한은 이벤트마다 if문으로 흩어 놓지 말고 현재 subscription snapshot으로 계산합니다.
function resolveEntitlement(input: {
status: string
priceIds: string[]
nextBilledAt: string | null
scheduledChange?: unknown
}) {
if (input.status === 'active' || input.status === 'trialing') return 'full_access'
if (input.status === 'past_due') return 'grace_access'
return 'no_paid_access'
}한국 결제수단 타입
한국 결제수단은 Paddle API와 webhook에 다음처럼 들어올 수 있습니다.
| 타입 | 표시명 |
|---|---|
south_korea_local_card | Korean local card |
kakao_pay | KakaoPay |
naver_pay | Naver Pay |
samsung_pay | Samsung Pay |
payco | Payco |
기존 korea_local 값은 과거 데이터에 그대로 남아 있으니, 분석 파이프라인에서는 historic value를 살려 두고
새 값은 별도 매핑 테이블로 흡수합니다.
장애 대응
| 장애 | 대응 |
|---|---|
| webhook 지연 | Paddle API 재조회로 subscription snapshot 확인 |
| webhook 중복 | notification_id로 무시 |
| 순서 뒤섞임 | occurred_at이 더 오래된 이벤트는 상태 덮어쓰기 금지 |
| 내부 처리 실패 | 원본 payload 재처리 큐 |
| Paddle API 장애 | 권한 상태를 마지막 정상 snapshot 기준으로 임시 유지 |
테이블 설계, raw body signature 검증, replay queue, snapshot reconcile은 Webhook 구현 부록에서 코드 수준으로 다룹니다.