Advanced

Claude Code के साथ Webhook

Claude Code का उपयोग करके webhook सीखें। Practical tips और code examples शामिल हैं।

Webhook क्या है

Webhookはevent発生時にHTTPrequestでबाहर部システムに通知する仕組み है।決済完了通知、Git pushevent、form送信 आदि多くの場面で使われ है।Claude Code का उपयोग करके、robustなWebhookimplementationをefficientlybuild किया जा सकता है。

Webhook受信側のimplementation

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

const app = express();

// rawBodyを保持するmiddleware
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());

  // 冪等性check
  const processed = await redis.get(`webhook:${event.id}`);
  if (processed) {
    return res.json({ status: "already_processed" });
  }

  try {
    await processWebhookEvent(event);

    // processing済み के रूप मेंマーク(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送信側のimplementation

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はリトライしない
          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" });
  }

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

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

  // シークレットはcreate時のみ返す
  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 fetch
    const configs = await prisma.webhookConfig.findMany({
      where: {
        active: true,
        events: { has: event },
      },
    });

    // 並列で送信(queueにडालनाcase में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 सेのutilization

WebhookimplementationをClaude Code को requestする例 है।asyncprocessingके बारे मेंはjobqueue・asyncprocessing、event設計はevent駆動アーキテクチャもदेखें。

Webhookシステムをimplement करो。
- 受信: Stripe/GitHub Webhookの署名検証
- 送信: HMAC署名付き、リトライfeatures付き
- 冪等性の保証
- 配信logとmonitoring
- 登録・management用のREST API

Webhookのsecurityके बारे मेंはOWASP Webhook Securityをदेखें。Claude Codeのutilization法はofficial documentationでconfirmでき है।

Summary

Webhookはシステムबीचintegrationのimportantなpattern है।Claude Code का उपयोग करके、署名検証、リトライ、冪等性保証を含むrobustなWebhook基盤をefficientlybuild किया जा सकता है。

#Claude Code #Webhook #API design #security #asyncprocessing