master-project
CHAPTER 32 / 50
읽기 약 2분
FUNCTION
웹훅: Stripe + 외부 서비스 연동
핵심 개념
서명 검증·idempotency·재시도·Stripe events 처리·event log — 안전한 웹훅 수신.
본문
웹훅 흐름
1. 외부 서비스 (Stripe·GitHub) 이벤트 발생
2. POST 요청 → 우리 endpoint
3. 서명 검증 (위변조 방지)
4. Idempotency 체크 (중복 처리 방지)
5. 비즈니스 로직 실행
6. 200 응답 (3초 안에 — 외부 서비스 timeout)Stripe Webhook 엔드포인트
// src/app/api/stripe/webhook/route.ts
import Stripe from 'stripe'
import { headers } from 'next/headers'
import { createServiceClient } from '@/lib/supabase/service'
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!)
const WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET!
export async function POST(request: Request) {
const body = await request.text() // 원본 body (서명 검증용)
const sig = (await headers()).get('stripe-signature')!
let event: Stripe.Event
try {
event = stripe.webhooks.constructEvent(body, sig, WEBHOOK_SECRET)
} catch (err) {
return new Response('signature mismatch', { status: 400 })
}
const supabase = createServiceClient() // service role
// Idempotency 체크
const { data: existing } = await supabase
.from('webhook_events')
.select('id')
.eq('event_id', event.id)
.single()
if (existing) return new Response('already processed', { status: 200 })
await supabase.from('webhook_events').insert({
event_id: event.id,
type: event.type,
payload: event.data.object,
})
// 이벤트별 처리
try {
switch (event.type) {
case 'checkout.session.completed':
await handleCheckoutCompleted(event.data.object as Stripe.Checkout.Session)
break
case 'customer.subscription.created':
case 'customer.subscription.updated':
await handleSubscriptionChange(event.data.object as Stripe.Subscription)
break
case 'customer.subscription.deleted':
await handleSubscriptionCanceled(event.data.object as Stripe.Subscription)
break
case 'invoice.payment_succeeded':
await handleInvoicePaid(event.data.object as Stripe.Invoice)
break
case 'invoice.payment_failed':
await handlePaymentFailed(event.data.object as Stripe.Invoice)
break
}
} catch (err) {
// Stripe가 재시도 → 200 안 보내면 자동 retry
return new Response('error', { status: 500 })
}
return new Response('ok', { status: 200 })
}webhook_events 테이블
create table webhook_events (
id uuid primary key default gen_random_uuid(),
event_id text unique not null, -- Stripe event ID
type text not null,
payload jsonb,
processed_at timestamptz default now()
);
create index idx_webhook_event_id on webhook_events(event_id);Subscription 처리
async function handleSubscriptionChange(sub: Stripe.Subscription) {
const supabase = createServiceClient()
// customer ID로 user 조회
const { data: user } = await supabase
.from('users')
.select('id')
.eq('stripe_customer_id', sub.customer as string)
.single()
if (!user) return
await supabase.from('subscriptions').upsert({
user_id: user.id,
stripe_subscription_id: sub.id,
plan: sub.items.data[0].price.lookup_key ?? 'pro',
status: sub.status,
current_period_end: new Date(sub.current_period_end * 1000).toISOString(),
}, { onConflict: 'stripe_subscription_id' })
// 알림 발송
await supabase.from('notifications').insert({
user_id: user.id,
type: 'subscription_updated',
title: '구독이 갱신되었습니다',
})
}로컬 개발 (Stripe CLI)
# Stripe CLI 설치
brew install stripe/stripe-cli/stripe
# 로그인
stripe login
# 웹훅 forward
stripe listen --forward-to localhost:3000/api/stripe/webhook
# 출력된 webhook signing secret을 .env.local에:
# STRIPE_WEBHOOK_SECRET=whsec_...
# 테스트 이벤트 trigger
stripe trigger payment_intent.succeedednext.config.ts 설정 (Body 파싱)
// Webhook은 raw body 필요 → middleware exclude
export const config = {
matcher: '/((?!api/stripe/webhook).*)',
}안티패턴
❌ 서명 검증 생략 → 위조 가능
❌ Idempotency 없음 → 중복 결제 처리
❌ 5초 이상 처리 → Stripe timeout (재시도 폭발)
❌ async 작업 동기 처리 → queue 사용
✓ 빠른 응답 + queue로 비동기 작업큐 사용 (큰 처리)
// inngest / trigger.dev / Vercel Queue
import { inngest } from '@/lib/inngest'
// webhook → queue
await inngest.send({
name: 'stripe.subscription.updated',
data: { subscriptionId: sub.id },
})
return new Response('ok') // 즉시 응답
// 별도 worker가 처리
inngest.createFunction({ id: 'process-sub' }, { event: 'stripe.subscription.updated' }, async ({ event }) => {
// 무거운 작업
})다음 챕터
CH.33 "백엔드 종합: API 전체 테스트".
AI 프롬프트
🤖 AI에게 잘 물어보는 법 — 모델·전략별 프롬프트
Claude
무료: Sonnet 4.6 / Pro $20/mo: Opus 4.6
내 마스터 프로젝트의 웹훅 처리 부분을 분석해서 실전 적용 + 개선 우선순위 3가지를 알려줘.
ChatGPT
무료: GPT-5.5 / Plus $20/mo: GPT-5.5 Pro
웹훅 처리 관련 모범 사례·안티패턴 5개를 비교 분석해서 실전 적용를 위한 추천 방안을 알려줘.
Gemini
무료: 2.5 Flash / Pro $19.99/mo: 3.1 Pro
내 프로젝트 전체에서 웹훅 처리 최적화 가능 위치와 리스크를 보고해줘.
Grok
무료: Grok 4.1 / SuperGrok $30/mo
2026년 한국 1인 개발자 시장의 웹훅 처리 트렌드와 차별화 포인트를 정리해줘.
⭐ 이것만 기억하세요
웹훅: Stripe + 외부 서비스 연동은 이 3가지만 확실히 잡으세요
1.서명 검증 + Idempotency = 안전한 웹훅
2.5초 안에 200 응답 + 무거운 작업은 큐로
3.다음 챕터에서 백엔드 종합
공유하기
진행도 32 / 50