Paddle 웹훅 vs 데이터베이스 동기화, 어느 쪽이 더 좋을까?
Source: Dev.to
Paddle 웹훅과 데이터베이스 동기화를 비교하여 청구 데이터를 PostgreSQL에 가져오는 방법을 알아봅니다. 각각 언제 사용하고, 언제 두 가지를 모두 사용해야 하는지 확인하세요.
작성자: Ilshaad Kheerdali · 2026년 6월 9일
Paddle Billing을 기반으로 애플리케이션을 구축하고 있다면, 대시보드 구동, 매출 정산, 혹은 청구 데이터를 제품 테이블과 조인하기 위해 결국 자체 데이터베이스에 해당 데이터를 저장해야 합니다. 이를 구현하는 방법은 크게 두 가지가 있습니다: 웹훅과 데이터베이스 동기화. 두 방법 모두 작동하지만 해결하는 문제가 다릅니다.
이 글에서는 Paddle에 특화된 각각의 접근 방식이 어떻게 동작하는지, 흔히 발생하는 문제는 무엇인지, 그리고 언제 어느 쪽을 선택해야 하는지를 자세히 살펴봅니다.
Paddle 웹훅은 어떻게 동작하나요?
Paddle 웹훅( Paddle에서는 notifications이라고 부릅니다) 은 푸시 기반입니다. Paddle 대시보드에서 알림 수신 엔드포인트를 설정하면, 거래가 완료되거나 구독이 취소되는 등 이벤트가 발생할 때마다 Paddle이 해당 이벤트 페이로드를 담은 HTTP POST 요청을 지정한 엔드포인트로 전송합니다.
아래는 공식 Paddle Node SDK를 사용한 Express 핸들러 예시입니다:
import express from 'express';
import { Paddle, EventName } from '@paddle/paddle-node-sdk';
const paddle = new Paddle(process.env.PADDLE_API_KEY);
const webhookSecret = process.env.PADDLE_WEBHOOK_SECRET;
app.post(
'/webhooks/paddle',
express.raw({ type: 'application/json' }),
async (req, res) => {
const signature = req.headers['paddle-signature'] as string;
const rawBody = req.body.toString();
let event;
try {
// Paddle‑Signature 헤더를 검증하고 페이로드를 파싱합니다
event = await paddle.webhooks.unmarshal(
rawBody,
webhookSecret,
signature,
);
} catch (err) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
switch (event.eventType) {
case EventName.TransactionCompleted:
// 거래 테이블에 삽입/업데이트
break;
case EventName.SubscriptionUpdated:
// 구독 테이블 업데이트
break;
case EventName.CustomerCreated:
// 고객 테이블에 삽입
break;
// ... 수십 가지 이벤트 타입을 추가로 처리
}
res.status(200).send('ok');
},
);
핵심은 이렇습니다. Paddle이 거의 실시간에 가깝게 이벤트를 푸시하고, 여러분의 서버가 이를 처리합니다.
Paddle 웹훅의 문제점
이론적으로 웹훅은 훌륭합니다. 하지만 실제 운영에서는 Paddle의 푸시 모델이 다음과 같은 흔한 운영상의 골칫거리를 동반합니다:
-
타임스탬프 기반 서명 검증
Paddle은Paddle-Signature헤더에 타임스탬프(ts)와 HMAC‑SHA256 해시(h1)를 포함해 각 요청을 서명합니다.ts:body형태로 서명된 페이로드를 재구성하고, 엔드포인트 비밀키로 해시한 뒤 비교해야 하며, 재생 공격을 방지하기 위해 오래된 타임스탬프는 거부해야 합니다. 원시 바디 처리 방식을 잘못하면(예: JSON을 너무 일찍 파싱해 바이트가 변함) 모든 서명이 실패합니다. -
샌드박스와 라이브가 완전히 분리됨
Paddle은 샌드박스와 프로덕션을 별도 환경으로 운영하며, API 키와 웹훅 비밀키가 각각 다릅니다. 흔히 겪는 장애 상황: 샌드박스에서는 정상 작동하지만, 라이브로 전환하면서 엔드포인트가 여전히 샌드박스 비밀키를 가리키면 프로덕션에서는 모든 이벤트가 조용히 사라집니다. -
다운타임 중 이벤트 손실
엔드포인트가 다운되거나 2xx가 아닌 응답을 반환하면 Paddle은 최대 3일 동안 백오프 방식으로 재시도합니다. 더 긴 사고, 잘못된 배포, 혹은 엔드포인트 URL 변경이 발생하면 해당 이벤트는 API를 통해 백필하지 않는 한 사라집니다. -
역사적 백필 불가
알림은 목적지가 생성된 시점부터 발생하는 이벤트만 캡처합니다. 지난해 거래 내역이 필요하다면 웹훅으로는 해결되지 않으며, Paddle API를 이용해 별도 백필 스크립트를 작성해야 합니다. -
이벤트 타입별 스키마 관리
transaction.completed,subscription.updated,adjustment.created,customer.updated등 각각 다른 페이로드 구조를 가집니다. 이를 지원하려면 많은 매핑 로직을 작성하고, Paddle API가 진화할 때마다 유지보수해야 합니다. -
재생(Replay) 복잡성
버그 이후 데이터를 재구축해야 할 때 Paddle 대시보드에서 알림을 재생할 수 있지만, 대규모로 수행하기는 번거롭고 “전체를 처음부터 다시 동기화”하는 네이티브 버튼은 없습니다.
데이터베이스 동기화는 어떻게 동작하나요?
데이터베이스 동기화는 정반대 접근 방식을 취합니다. Paddle이 이벤트를 푸시하는 대신, 동기화 서비스가 주기적으로 Paddle API에서 데이터를 pull 하여 직접 PostgreSQL에 기록합니다.
흐름은 다음과 같습니다:
- PostgreSQL 데이터베이스에 연결 (Supabase, Neon, Railway, AWS RDS 등)
- Paddle API 키(읽기 전용) 제공
- 동기화 서비스가 Paddle API를 호출해 데이터를 가져오고, 구조화된 테이블에 기록
- 이후 실행 시 변경된 부분만 가져와 upsert 수행 → 중복·누락 없음
알림 엔드포인트를 만들 필요도, Paddle-Signature를 검증할 필요도, 샌드박스/라이브 비밀키 불일치를 디버깅할 필요도 없습니다. 데이터가 바로 데이터베이스에 나타나 쿼리할 준비가 됩니다.
비교 표
| Factor | Paddle 웹훅 | 데이터베이스 동기화 |
|---|---|---|
| 데이터 신선도 | 거의 실시간(초 단위) | 배치(예: 6시간마다 / 일간) |
| 설정 복잡도 | 높음 – 엔드포인트, 서명·타임스탬프 검증, 이벤트 처리 | 낮음 – DB와 API 키 연결 |
| 역사 데이터 | 없음 – 신규 이벤트만 캡처 | 있음 – 첫 동기화 시 전체 백필 |
| 샌드박스 vs 라이브 | 별도 비밀키, 구성 실수 위험 | 환경당 하나의 API 키, 엔드포인트 불일치 없음 |
| 신뢰성 | 재시도·멱등성·실패 처리 필요 | 동기화 서비스가 관리 |
| 유지보수 | 지속적 – 새로운 이벤트 타입·API 변경 | 최소 – 스키마가 자동 처리 |
| 필요 코드 | 많음 – 핸들러, 매핑, 검증 | 없음(노코드) 혹은 최소 |
| 최적 활용 | 실시간 액션 트리거 | 데이터 조회·분석 |
언제 Paddle 웹훅을 사용해야 할까?
웹훅은 실시간으로 이벤트에 반응해야 할 때 적합합니다:
- 구독이 활성화될 때 즉시 접근 권한 부여 (
subscription.activated) - 구독이 취소되거나 결제가 실패했을 때 접근 권한 회수 (
subscription.canceled,transaction.payment_failed) - 거래가 완료되면 영수증·환영 이메일 전송
- 구독이 취소될 때 이탈 설문 트리거
- 결제가 성공하면 UI 즉시 업데이트
“Paddle에서 X가 발생하면 Y를 바로 수행한다”는 시나리오라면 웹훅이 정답이며, 실시간 푸시 모델이 이를 위해 설계되었습니다.
언제 데이터베이스 동기화를 사용해야 할까?
데이터베이스 동기화는 쿼리·분석·조인이 필요할 때 적합합니다:
- 대시보드 구축 – 매출 추이, MRR, 이탈률, 고객 성장 등
- 즉석 쿼리 – “이번 분기에 £1,000 이상 사용한 고객은 누구인가?”
- 청구 데이터와 애플리케이션 데이터 조인 – Paddle 고객과 자체 사용자 테이블 결합
- 보고서 생성 – 회계·투자자 업데이트용 보고서
- 내부 도구 – 팀이 청구 이력을 조회해야 할 때
Paddle 데이터가 PostgreSQL에 동기화되면 다음과 같은 쿼리를 바로 실행할 수 있습니다:
-- 지난 6개월간 월별 완료된 거래 매출
SELECT
DATE_TRUNC('month', created_at) AS month,
COUNT(*) AS transaction_count,
SUM(grand_total) / 100.0 AS revenue
FROM paddle_transactions
WHERE status = 'completed'
AND created_at >= NOW() - INTERVAL '6 months'
GROUP BY month
ORDER BY month DESC;
-- 활성 구독을 자체 사용자 테이블과 조인
SELECT u.email, ps.status, ps.next_billed_at
FROM users u
JOIN paddle_customers pc ON pc.email = u.email
JOIN paddle_subscriptions ps ON ps.customer_id = pc.id
WHERE ps.status = 'active'