웹훅·권한 동기화
서로 다른 서명·이벤트·메타데이터를 동일한 멱등 권한 모델에 연결한다. 실제 운영 기준과 검증 항목을 함께 제공한다.
이 장은 두 서비스를 같은 주제에서 비교한 뒤 두 공급자의 상세 설명을 모두 담습니다. 기능 노출·계약 요율·국가별 결제수단은 계정과 시점에 따라 달라질 수 있으므로 실제 스토어와 각 절의 공식 출처를 확인하세요.
주제별 비교
| 비교 항목 | Paddle | Lemon Squeezy |
|---|---|---|
| 서명 | Paddle-Signature의 timestamp·HMAC을 검증한다. | X-Signature의 raw body HMAC-SHA256을 검증한다. |
| 식별 | event_id·notification_id와 transaction/subscription ID를 저장한다. | X-Event-Name, JSON:API data ID와 meta.custom_data를 저장한다. |
| 최종 상태 | 최신 Paddle API를 조회해 권한을 결정한다. | 최신 Lemon Squeezy API를 조회해 권한을 결정한다. |
Paddle 상세
핵심 요약
- 제품 권한은 Checkout 성공이 아니라 서버에서 검증된 webhook 이벤트로만 열어야 합니다.
- 구독 권한의 핵심 이벤트는
subscription.created/updated, 일회성 fulfillment는transaction.completed이며 환불·chargeback에는adjustment.created/updated가 필요합니다. event_id로 비즈니스 이벤트 중복을 막고,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 공식 provisioning 가이드는 구독 권한에 subscription.created와 subscription.updated를,
일회성 상품 fulfillment에는 transaction.completed를 사용합니다. 환불·chargeback·reversal을 구독 상태만으로
표현할 수 없으므로 adjustment 이벤트를 별도로 처리합니다.
| 이벤트 | 목적 |
|---|---|
subscription.created | subscription ID 연결, trialing/active 권한 계산 |
subscription.updated | status, items, next billing date 반영 |
transaction.completed | 일회성 상품 fulfillment, 구독 결제의 재무 상태 연결 |
adjustment.created | refund·chargeback·credit·reversal 요청/생성 기록 |
adjustment.updated | pending approval, approved, rejected 등 최종 상태와 권한 조정 |
운영 분석을 더 촘촘히 하려면 transaction created/updated와 payout 이벤트를 추가하되, 권한 규칙과 재무 원장 규칙을 분리합니다.
저장해야 할 필드
| 필드 | 이유 |
|---|---|
event_id | 동일 Paddle 이벤트의 비즈니스 중복 처리 방지 |
notification_id | destination별 전달·재전송 추적 |
occurred_at | 이벤트 순서 보정 |
paddle_customer_id | 고객 매핑 |
paddle_subscription_id | 구독 조회와 상태 동기화 |
paddle_transaction_id | 결제/환불/대사 연결 |
subscription.status | 권한 상태 결정 |
subscription.consent_requirements | 한국 무료체험·도입 할인 종료 동의 상태 |
subscription.items[].price.id | 플랜/권한 매핑 |
transaction.details.totals | 마지막 결제 금액 표시 |
payments[].method_details.type | 결제수단 분석과 CS |
adjustment.id/action/status | 환불·chargeback·reversal 상태와 권한/원장 연결 |
처리 흐름
멱등성 원칙
1. webhook signature 검증
2. event_id 중복 확인, 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으로 계산합니다. 다만 승인된 환불이나 chargeback처럼 subscription status만으로 보이지 않는 재무 사건은 adjustment snapshot을 함께 적용합니다.
function resolveEntitlement(input: {
status: string
priceIds: string[]
nextBilledAt: string | null
scheduledChange?: unknown
revokesEntitlement?: boolean
}) {
if (input.revokesEntitlement) return 'no_paid_access'
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 중복 | event_id로 비즈니스 처리 중복 방지, notification 이력은 보존 |
| 순서 뒤섞임 | occurred_at이 더 오래된 이벤트는 상태 덮어쓰기 금지 |
| 내부 처리 실패 | 원본 payload 재처리 큐 |
| Paddle API 장애 | 권한 상태를 마지막 정상 snapshot 기준으로 임시 유지 |
테이블 설계, raw body signature 검증, replay queue, snapshot reconcile은 Webhook 구현 부록에서 코드 수준으로 다룹니다.
참고 자료
- Paddle Developer - Webhooks
- Paddle Developer - Handle provisioning and fulfillment
- Paddle Developer - subscription.created
- Paddle Developer - adjustment.created
- Paddle Developer - adjustment.updated
- Paddle Developer - Korean subscription consent requirements
- Paddle Developer - Improved Korean payment methods
Lemon Squeezy 상세
핵심 요약
X-Signature는 raw body의 HMAC-SHA256 해시이며X-Event-Name이 이벤트 종류를 알려준다.- checkout의 custom data는 webhook 최상위
meta.custom_data로 전달된다. - 수신 확인과 제품 권한 반영을 분리하고 재시도·중복·역순 이벤트를 처리한다.
- Lemon Squeezy webhook에 다른 플랫폼의
event_id나notification_id가 있다고 가정하지 않는다.
구독·주문 이벤트
| 목적 | 관찰할 이벤트 | 후속 조회 |
|---|---|---|
| 신규 구매 | order_created, subscription_created | order·subscription 객체 |
| 갱신 성공·실패 | subscription_payment_success, subscription_payment_failed, subscription_payment_recovered | 구독 최신 상태, invoice |
| 플랜·상태 변경 | subscription_updated, subscription_paused, subscription_unpaused, subscription_resumed | 구독 최신 상태 |
| 해지·만료 | subscription_cancelled, subscription_expired | ends_at, 상태 |
| 환불 | order_refunded, subscription_payment_refunded | 주문/구독 invoice·환불 내역 |
이벤트 목록은 Event types를 기준으로 구현 시 확인합니다. 이벤트가 왔다고 즉시 임의의 권한 전이를 확정하기보다 관련 리소스의 최신 snapshot을 조회합니다.
수신 처리
- HTTPS endpoint에서 raw body를 읽고
X-Signature를 공식 서명 방식으로 비교합니다. - 헤더
X-Event-Name, body의meta.event_name,data.type/id,attributes.test_mode를 검증합니다. - raw payload와 수신 시각을 저장하고, 이벤트명·리소스 ID·payload digest를 중복 방지 키로 사용합니다.
- 영속 저장에 성공하면 신속히
200을 반환하고 worker에서 API 조회·권한 재계산을 수행합니다. - 처리 실패는 내부 retry queue로 복구합니다. 플랫폼은 실패 응답에 최대 3번 추가 재시도하므로 그것만 믿지 않습니다.
Webhook requests는 JSON:API 형태와 meta.custom_data, 헤더, 재시도 정책을 명시합니다. custom data의 workspace ID는 현재 로그인 사용자와 서버에서 대조해야 하며 클라이언트가 임의로 입력한 값만으로 다른 테넌트에 권한을 열면 안 됩니다.
권한 정책
| 구독 상태 | 앱 정책 예시 |
|---|---|
on_trial, active | 해당 variant 권한 제공 |
past_due | 플랫폼 재시도 기간을 고려해 안내와 grace 처리 |
paused, unpaid | 기능 제한 여부를 제품 정책으로 명시 |
cancelled | ends_at까지 제공 여부를 구매 약속과 일치시킴 |
expired | 유료 권한 종료 |
환불은 구독 상태와 별도입니다. 부분 환불을 전체 권한 제거와 동일시하지 않고 환불 금액·대상 주문·서비스 정책을 확인합니다.
장애·대사
- 서명 실패율, 저장 실패율, worker 지연, API 조회 실패, 권한 불일치를 모니터링합니다.
- 동일 이벤트 재전송, 실패 이벤트 재처리, 구독 상태 역순 도착을 테스트합니다.
- 일일 subscription 조회와 내부 entitlement 상태 비교로 유실을 보정합니다.
- test mode의 시뮬레이션 후 live 소액 구매·환불까지 확인합니다.