본문으로 바로가기
리옵트 핸드북
리옵트 핸드북
글로벌 프로덕트 결제 가이드

전략·계약 구조

의사결정 맵국내 PG 해외결제 vs 해외 MoRMoR 계약 흐름계약·데이터 보정 워크시트온보딩 준비

결제 구현

상품·가격·세금 카탈로그한국 Checkout 현지화한국 Checkout QA 매트릭스구독 라이프사이클웹훅과 권한 동기화Webhook 구현 부록

운영·컴플라이언스

한국 세무·회계개인사업자 vs 법인 수익·수수료·과세 분석세무·법무 전달 패키지정산·대사정산 CSV·전표 템플릿약관·소비자·개인정보한국 B2B·Paddle Invoicing 청구 플로우CS·분쟁·리스크

사례·검증

사례·체크리스트검증 리포트업데이트 내역
핸드북›글로벌 프로덕트 결제 가이드›Webhook 구현 부록

Webhook 구현 부록

Paddle webhook 원본 저장, signature 검증, 멱등성 테이블, replay queue, snapshot reconcile 구현 가이드

핵심 요약

  • 성공 redirect나 client-side callback이 아니라 서버에서 검증한 webhook과 Paddle API snapshot을 기준으로 권한을 재계산한다.
  • SDK 또는 동등한 구현으로 raw body, timestamp, 모든 h1 서명을 검증하고, event_id unique로 비즈니스 중복 처리를 막으며 5초 내 200을 응답한다.
  • occurred_at을 비교해 늦게 도착한 과거 이벤트가 최신 상태를 덮어쓰지 않게 한다.
  • 권한은 이벤트 타입별 if문이 아니라 subscription snapshot에서 파생(resolveEntitlement)한다.
  • failed retry·manual replay·일일 subscription reconcile·월마감 payout reconcile로 누락과 불일치를 보정한다.

Paddle webhook 구현은 결제 기능을 떠받치는 인프라입니다. 성공 redirect나 client-side callback, checkout close event를 권한 부여의 근거로 삼지 말고, 서버에서 검증한 webhook과 Paddle API snapshot을 기준으로 권한을 다시 계산합니다.

Production credential과 API 기준

항목운영 기준
API keybackend 전용, 최소 권한·만료일·소유자 지정, secret manager 저장, 정기 회전
Client-side tokenPaddle.js frontend용. API key와 분리하고 sandbox/live token을 혼용하지 않음
Webhook secretendpoint별 별도 저장, rotation 시 복수 h1 서명 검증
API versionPaddle-Version: 1을 명시적으로 고정하고 changelog 기반 upgrade review
Checkout domainlive checkout에 사용할 domain approval 상태를 배포 전 확인
Rate limit429와 Retry-After를 존중하고 지수 backoff·jitter 적용
Paginationcursor의 has_more와 next를 끝까지 순회하고 전체 개수를 선행 가정하지 않음

대부분의 Paddle API는 IP 기준 분당 240회, preview endpoint는 분당 1,000회 제한으로 안내됩니다. 일부 subscription 즉시 청구 변경은 구독별 시간·일 단위 제한이 별도로 있으므로 대량 migration에서 일반 API 제한만 보면 안 됩니다. report API도 생성 한도가 있으므로 월마감 job은 중복 생성을 피하고 기존 report를 재사용합니다.

최소 테이블

create table paddle_webhook_events (
  id bigserial primary key,
  notification_id text not null unique,
  event_id text not null unique,
  event_type text not null,
  occurred_at timestamptz not null,
  payload jsonb not null,
  signature_header text not null,
  processing_status text not null default 'pending',
  retry_count integer not null default 0,
  last_error text,
  received_at timestamptz not null default now(),
  processed_at timestamptz
);

create index paddle_webhook_events_status_idx
  on paddle_webhook_events (processing_status, received_at);

create index paddle_webhook_events_event_id_idx
  on paddle_webhook_events (event_id);

create table billing_subscriptions (
  id bigserial primary key,
  workspace_id text not null,
  paddle_customer_id text,
  paddle_subscription_id text unique,
  paddle_status text not null,
  current_price_id text,
  next_billed_at timestamptz,
  entitlement_status text not null,
  last_event_occurred_at timestamptz,
  updated_at timestamptz not null default now()
);

endpoint 처리 원칙

export async function POST(request: Request) {
  const rawBody = await request.text()
  const signature = request.headers.get('Paddle-Signature')
  const endpointSecret = process.env.PADDLE_WEBHOOK_SECRET

  if (!signature || !endpointSecret) return new Response('Invalid webhook configuration', { status: 400 })

  // Paddle SDK verifier 또는 아래 검증 요건을 충족한 함수만 사용
  verifyPaddleSignature({ rawBody, signature, endpointSecret, toleranceSeconds: 5 })

  const event = JSON.parse(rawBody)
  await saveEventIfNew({
    notificationId: event.notification_id,
    eventId: event.event_id,
    eventType: event.event_type,
    occurredAt: event.occurred_at,
    payload: event,
    signatureHeader: signature,
  })

  await enqueueBillingJob(event.notification_id)

  return new Response('ok', { status: 200 })
}
원칙이유
raw body로 signature 검증JSON parse 이후 문자열이 바뀌면 서명 검증이 깨질 수 있음
timestamp tolerance 검증오래된 정상 요청의 replay 차단. SDK 기본 tolerance는 5초
모든 h1 후보 비교secret rotation 중 복수 서명이 올 수 있음
constant-time HMAC 비교비교 과정의 timing leak 방지
event_id unique같은 Paddle event의 비즈니스 처리 중복 방지
notification_id 저장destination별 전달과 수동 replay 추적
5초 내 200 응답Paddle delivery retry와 timeout 방지
후속 작업은 queue 처리이메일, CRM, 회계 연동 실패가 webhook 응답을 막지 않게 함
occurred_at 비교늦게 도착한 과거 이벤트가 최신 상태를 덮어쓰지 않게 함

Paddle-Signature는 ts와 하나 이상의 h1 값으로 구성됩니다. 서명 대상은 raw body를 사용한 ts:rawBody이고 HMAC-SHA256으로 계산합니다. endpoint secret을 API key나 client-side token과 혼동하지 말고 secret manager에 보관하며, 서버 시각이 tolerance 안에 들도록 NTP를 유지합니다. 가능하면 직접 파서를 작성하지 말고 Paddle 공식 SDK verifier를 사용합니다.

worker 처리 흐름

권한 계산 함수

권한은 이벤트 타입별 if문으로 직접 열고 닫지 말고 subscription snapshot에서 파생합니다.

type EntitlementStatus = 'full_access' | 'grace_access' | 'no_paid_access'

function resolveEntitlement(snapshot: {
  status: string
  currentPriceIds: string[]
  nextBilledAt: string | null
  scheduledChange?: { action: string; effectiveAt: string } | null
}): EntitlementStatus {
  if (snapshot.status === 'active' || snapshot.status === 'trialing') {
    return 'full_access'
  }

  if (snapshot.status === 'past_due') {
    return 'grace_access'
  }

  return 'no_paid_access'
}

환불·chargeback·reversal은 subscription status와 별도인 adjustment입니다. worker가 adjustment.created/updated를 받으면 최신 adjustment를 조회하고 action, status, partial/full 금액을 내부 결제 원장에 기록한 뒤 상품 정책에 따라 credit 또는 entitlement를 조정합니다. 부분 환불만으로 전체 구독 권한을 제거하지 않습니다.

replay와 reconcile

작업주기구현
failed event retry5분마다processing_status = 'failed' 재시도
manual replay운영자 실행notification_id 기준 worker 재실행
subscription reconcile매일Paddle subscription list와 내부 상태 비교
payout reconcile월마감transaction/payout reconciliation report와 내부 주문 매칭
stale checkout cleanup매일결제 생성 후 완료되지 않은 transaction 정리

테스트 시나리오

시나리오기대 결과
같은 notification_id 두 번 수신두 번째 이벤트는 저장/처리 중복 없음
같은 event_id가 다른 delivery로 수신비즈니스 side effect는 한 번만 실행
timestamp tolerance를 벗어난 서명400 응답, payload 저장·처리하지 않음
secret rotation 중 복수 h1유효한 후보 하나가 일치하면 승인
subscription.updated가 subscription.created보다 먼저 도착snapshot 조회로 최종 상태 정상 반영
worker 중간 실패event는 failed, retry 후 처리
Paddle API 일시 장애권한을 마지막 정상 snapshot 기준으로 유지
unknown payment method type원본 저장, 분석 테이블에는 unknown으로 표시
성공 redirect 누락webhook만으로 권한 부여
부분 환불 승인환불액·credit만 조정하고 전체 권한은 정책에 따라 유지

참고 자료

  • Paddle Developer - Webhooks overview
  • Paddle Developer - Signature verification
  • Paddle Developer - Handle webhook delivery
  • Paddle Developer - Handle provisioning and fulfillment
  • Paddle Developer - adjustment.created
  • Paddle Developer - adjustment.updated
  • Paddle Developer - API authentication
  • Paddle Developer - Client-side tokens
  • Paddle Developer - API versioning
  • Paddle Developer - Rate limiting
  • Paddle Developer - Pagination
  • Paddle Developer - Checkout domains API
  • Paddle Developer - Report creation limit

관련 문서

검증 리포트

Paddle 글로벌 결제 가이드의 공식 문서 기준일, 검증 범위, 한계

웹훅과 권한 동기화

Paddle 이벤트 저장, 멱등성, 상태 전이, 한국 결제수단 타입 변경 대응

템플릿 & 도구

SaaS 유료 플랜 설계 · 비용에서 최소 가격을 도출하는 계산 워크시트, Good-Better-Best 티어 캔버스, 경쟁사 분석 매트릭스, 가격 인상 이메일과 런칭 전·분기별 리뷰 체크리스트를 복사해 바로 씁니다.

수익화 포트폴리오

Android 앱 엔터프라이즈 운영 · one-time product, subscription, base plan, offer 조합을 운영 관점에서 설계하는 기준

결제 UX 설계

SaaS 유료 플랜 설계 · 가격 페이지 베스트 프랙티스, 인디해커 친화 결제 플랫폼, 구독 관리 셀프서브

웹훅과 권한 동기화

Paddle 이벤트 저장, 멱등성, 상태 전이, 한국 결제수단 타입 변경 대응

한국 세무·회계

한국 법인의 부가세·영세율 판단, 매출 인식, 증빙, 세무사 검토 체크리스트

On this page

Production credential과 API 기준최소 테이블endpoint 처리 원칙worker 처리 흐름권한 계산 함수replay와 reconcile테스트 시나리오참고 자료