Webhook: Claude Code 활용 가이드
webhook: Claude Code 활용. 실용적인 팁과 코드 예시를 포함합니다.
Webhook とは
Webhookは이벤트発生時にHTTP요청で外部システムに알림する仕組みです。決済완료알림、Git push이벤트、폼전송など多くの場面で使われます。Claude Code를 활용하면 堅牢なWebhook구현を효율적으로구축할 수 있습니다。
Webhook수신側の구현
import express from "express";
import crypto from "crypto";
const app = express();
// rawBodyを保持する미들웨어
app.use("/webhooks", express.raw({ type: "application/json" }));
// 署名검증
function verifyWebhookSignature(
payload: Buffer,
signature: string,
secret: string
): boolean {
const expected = crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(`sha256=${expected}`)
);
}
app.post("/webhooks/stripe", async (req, res) => {
const signature = req.headers["stripe-signature"] as string;
const secret = process.env.STRIPE_WEBHOOK_SECRET!;
// 署名검증
if (!verifyWebhookSignature(req.body, signature, secret)) {
return res.status(401).json({ error: "Invalid signature" });
}
const event = JSON.parse(req.body.toString());
// 冪等性チェック
const processed = await redis.get(`webhook:${event.id}`);
if (processed) {
return res.json({ status: "already_processed" });
}
try {
await processWebhookEvent(event);
// 処理済み로서マーク(24시간保持)
await redis.set(`webhook:${event.id}`, "1", "EX", 86400);
res.json({ status: "processed" });
} catch (error) {
console.error("Webhook processing failed:", error);
res.status(500).json({ error: "Processing failed" });
}
});
async function processWebhookEvent(event: any) {
switch (event.type) {
case "payment_intent.succeeded":
await handlePaymentSuccess(event.data.object);
break;
case "customer.subscription.updated":
await handleSubscriptionUpdate(event.data.object);
break;
case "invoice.payment_failed":
await handlePaymentFailed(event.data.object);
break;
default:
console.log(`Unhandled event type: ${event.type}`);
}
}
Webhook전송側の구현
interface WebhookConfig {
id: string;
url: string;
secret: string;
events: string[];
active: boolean;
}
class WebhookSender {
private maxRetries = 3;
private retryDelays = [1000, 5000, 30000]; // ms
async send(
config: WebhookConfig,
event: string,
payload: Record<string, unknown>
): Promise<void> {
const body = JSON.stringify({
id: crypto.randomUUID(),
event,
timestamp: new Date().toISOString(),
data: payload,
});
const signature = this.sign(body, config.secret);
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
try {
const response = await fetch(config.url, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-Webhook-Signature": `sha256=${signature}`,
"X-Webhook-Event": event,
"X-Webhook-Delivery": crypto.randomUUID(),
},
body,
signal: AbortSignal.timeout(10000),
});
if (response.ok) {
await this.logDelivery(config.id, event, "success", attempt);
return;
}
if (response.status >= 400 && response.status < 500) {
// 클라이언트에러はリトライしない
await this.logDelivery(config.id, event, "client_error", attempt);
return;
}
throw new Error(`HTTP ${response.status}`);
} catch (error) {
if (attempt < this.maxRetries) {
await new Promise((r) => setTimeout(r, this.retryDelays[attempt]));
} else {
await this.logDelivery(config.id, event, "failed", attempt);
throw error;
}
}
}
}
private sign(payload: string, secret: string): string {
return crypto
.createHmac("sha256", secret)
.update(payload)
.digest("hex");
}
private async logDelivery(
configId: string,
event: string,
status: string,
attempts: number
) {
await prisma.webhookDelivery.create({
data: { configId, event, status, attempts },
});
}
}
Webhook등록API
const router = express.Router();
router.post("/api/webhooks", async (req, res) => {
const { url, events } = req.body;
// URLの검증
try {
const parsed = new URL(url);
if (parsed.protocol !== "https:") {
return res.status(400).json({ error: "HTTPS required" });
}
} catch {
return res.status(400).json({ error: "Invalid URL" });
}
// シークレットの생성
const secret = crypto.randomBytes(32).toString("hex");
const webhook = await prisma.webhookConfig.create({
data: {
url,
events,
secret,
active: true,
userId: req.user!.id,
},
});
// シークレットは생성時のみ返す
res.status(201).json({
id: webhook.id,
url: webhook.url,
events: webhook.events,
secret,
});
});
이벤트のディスパッチ
class EventDispatcher {
private sender = new WebhookSender();
async dispatch(event: string, payload: Record<string, unknown>) {
// 該当이벤트を購読しているWebhookを취득
const configs = await prisma.webhookConfig.findMany({
where: {
active: true,
events: { has: event },
},
});
// 병렬で전송(큐に入れる場合はBullMQを使用)
const results = await Promise.allSettled(
configs.map((config) => this.sender.send(config, event, payload))
);
const failed = results.filter((r) => r.status === "rejected");
if (failed.length > 0) {
console.error(`${failed.length}/${configs.length} webhooks failed`);
}
}
}
// Usage example
const dispatcher = new EventDispatcher();
// 注文완료時
async function completeOrder(orderId: string) {
const order = await prisma.order.update({
where: { id: orderId },
data: { status: "completed" },
});
await dispatcher.dispatch("order.completed", {
orderId: order.id,
total: order.total,
customerId: order.customerId,
});
}
Claude Code로の활용
Webhook구현をClaude Code에依頼する例です。비동기処理에 대해서는잡큐・비동기処理、이벤트설계は이벤트駆動아키텍처도 참고하세요.
Webhookシステムを実装して。
- 受信: Stripe/GitHub Webhookの署名検証
- 送信: HMAC署名付き、リトライ機能付き
- 冪等性の保証
- 配信ログとモニタリング
- 登録・管理用のREST API
Webhookの보안에 대해서는OWASP Webhook Security를 참고하세요.Claude Code의 활용법은공식 문서에서 확인할 수 있습니다.
정리
Webhookはシステム間연동の重要なパターンです。Claude Code를 활용하면 署名검증、リトライ、冪等性保証を含む堅牢なWebhook基盤を효율적으로구축할 수 있습니다。
Claude Code 워크플로우를 한 단계 업그레이드하세요
지금 바로 Claude Code에 복사해 쓸 수 있는 검증된 프롬프트 템플릿 50선.
이 글을 작성한 사람
Masa
Claude Code를 적극 활용하는 엔지니어. 10개 언어, 2,000페이지 이상의 테크 미디어 claudecode-lab.com을 운영 중.
관련 글
Claude Code Agent SDK 입문 ― 자율 에이전트를 빠르게 구축하는 방법
Claude Code Agent SDK로 자율형 AI 에이전트를 구축하는 방법을 해설합니다. 설정부터 도구 정의, 멀티스텝 실행까지 실전 코드와 함께 소개합니다.
Claude Code 컨텍스트 관리 테크닉 완전 가이드
Claude Code의 컨텍스트 윈도우를 최대한 활용하는 실전 테크닉을 해설합니다. 토큰 절약, 대화 분할, CLAUDE.md 활용법까지 소개합니다.
Claude Code MCP Server 설정 및 실전 활용 가이드
Claude Code의 MCP Server 기능을 종합적으로 소개합니다. 외부 도구 연결, 서버 설정, 실전 통합 사례까지 한 번에 알아보세요.