Advanced

Webhook dengan Claude Code

Pelajari tentang webhook menggunakan Claude Code. Dilengkapi tips praktis dan contoh kode.

Webhook

Webhook event発生時 HTTPrequest 外部sistem notifikasi mekanisme.決済selesainotifikasi、Git pushevent、formpengiriman dll.多く 場面 使われ.Claude Code 使えば、robustなWebhookimplementasi efisien pembangunan bisa dilakukan.

implementasi Webhookpenerimaan側

import express from "express";
import crypto from "crypto";

const app = express();

// rawBody 保持middleware
app.use("/webhooks", express.raw({ type: "application/json" }));

// 署名verifikasi
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!;

  // 署名verifikasi
  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);

    // pemrosesan済み dan マーク(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}`);
  }
}

implementasi Webhookpengiriman側

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) {
          // clienterror リトライし tidak
          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 },
    });
  }
}

WebhookregistrasiAPI

const router = express.Router();

router.post("/api/webhooks", async (req, res) => {
  const { url, events } = req.body;

  // URL verifikasi
  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" });
  }

  // シークレット generate
  const secret = crypto.randomBytes(32).toString("hex");

  const webhook = await prisma.webhookConfig.create({
    data: {
      url,
      events,
      secret,
      active: true,
      userId: req.user!.id,
    },
  });

  // シークレット pembuatan時 み返す
  res.status(201).json({
    id: webhook.id,
    url: webhook.url,
    events: webhook.events,
    secret,
  });
});

ディスパッチ event

class EventDispatcher {
  private sender = new WebhookSender();

  async dispatch(event: string, payload: Record<string, unknown>) {
    // 該当event 購読 Webhook pengambilan
    const configs = await prisma.webhookConfig.findMany({
      where: {
        active: true,
        events: { has: event },
      },
    });

    // 並列 pengiriman(キュー 入れる場合 BullMQ penggunaan)
    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();

// 注文selesai時
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,
  });
}

Pemanfaatan dengan Claude Code

Webhookimplementasi Claude Code 依頼 例.非同期pemrosesan mengenai ジョブキュー・非同期pemrosesan、event設計 event駆動arsitektur juga bisa dijadikan referensi.

Webhooksistem implementasikan.
- penerimaan: Stripe/GitHub Webhook 署名verifikasi
- pengiriman: HMAC署名付き、リトライ機能付き
- 冪等性 保証
- distribusiログ dan monitoring
- registrasi・manajemen用 REST API

Mengenai Webhookのセキュリティ, konfirmasiできます。 di OWASP Webhook Securityを参照してください。Claude Codeの活用法は公式ドキュメント.

Summary

Webhook sistem間integrasi pentingなpola.Claude Code 使えば、署名verifikasi、リトライ、冪等性保証 含むrobustなWebhookfondasi efisien pembangunan bisa dilakukan.

#Claude Code #Webhook #API design #security #非同期pemrosesan