Claude Code से production Webhook बनाएं: signature, idempotency और retries
Claude Code से production Webhook बनाएं: raw body, signature verification, idempotency, retries, tests और runbook.
Webhook वह तरीका है जिससे कोई external service event होने पर आपकी application को HTTP request भेजती है। Payment complete होना, GitHub push, form submission, subscription change, CRM update या SaaS status change जैसे cases में Webhook बहुत उपयोगी होता है।
Production में Webhook सिर्फ JSON पढ़ने वाला POST endpoint नहीं है। आपको raw body यानी request body के original bytes बचाने होते हैं, provider की signature verify करनी होती है, idempotency रखनी होती है ताकि वही event दोबारा आए तो business action एक बार ही चले, और heavy काम को retry queue में भेजना होता है। Failure के बाद replay tool और runbook भी चाहिए।
अगर आप Claude Code को केवल “Webhook बना दो” कहेंगे, तो demo जल्दी बन सकता है, लेकिन वह JSON parse पहले कर सकता है या duplicate delivery को दो बार process कर सकता है। बेहतर prompt में failure rules भी लिखें। Related guides: Claude Code API development, Secrets management, security best practices, और queue system।
Provider contract
| Item | GitHub example | Stripe example | Implementation point |
|---|---|---|---|
| Endpoint | POST /webhooks/github | POST /webhooks/stripe | अलग route |
| Event ID | X-GitHub-Delivery | event.id | idempotency key |
| Event type | X-GitHub-Event | event.type | handler routing |
| Signature header | X-Hub-Signature-256 | Stripe-Signature | authenticity check |
| Verification input | raw body | raw body | body parser order |
| Success response | fast 2xx | fast 2xx | save then queue |
Official references साथ रखें: GitHub Webhooks, GitHub delivery validation, Stripe Webhooks, Stripe webhook signatures, Express express.raw, और Claude Code best practices।
flowchart LR
A["Provider<br/>GitHub / Stripe"] --> B["Webhook endpoint<br/>raw body"]
B --> C["Signature verification"]
C --> D["Event store"]
D --> E["Idempotency check"]
E --> F["Retry queue"]
F --> G["Domain handler"]
D --> H["Replay tool"]
Claude Code prompt
Express + TypeScript में GitHub Webhook receiver implement करें।
Requirements:
- POST /webhooks/github add करें
- Webhook routes पर express.raw({ type: "*/*" }) से raw body preserve करें
- JSON parse signature verification के बाद करें
- X-Hub-Signature-256 को HMAC SHA-256 से verify करें
- X-GitHub-Delivery को idempotency key बनाएं
- Accepted event को processing से पहले event store में save करें
- Same delivery id को दो बार process न करें
- Quickly 202 return करें और heavy work retry queue में process करें
- node:test से valid signature, invalid signature और duplicate delivery cover करें
- Saved deliveries के लिए replay script add करें
- Secret को WEBHOOK_SECRET environment variable से पढ़ें
Runnable receiver
npm init -y
npm install express
npm install -D typescript tsx @types/node @types/express
src/server.ts बनाएं:
import crypto from "node:crypto";
import express from "express";
type EventStatus = "queued" | "processing" | "processed" | "failed";
type WebhookEvent = {
id: string;
provider: "github";
type: string;
headers: Record<string, string>;
rawBody: Buffer;
payload: unknown;
receivedAt: string;
status: EventStatus;
attempts: number;
lastError?: string;
};
export const app = express();
export const eventStore = new Map<string, WebhookEvent>();
export const processedEvents = new Set<string>();
export const retryQueue: string[] = [];
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET ?? "dev-secret-change-me";
app.use("/webhooks", express.raw({ type: "*/*", limit: "1mb" }));
app.use(express.json());
function firstHeader(value: string | string[] | undefined): string | undefined {
return Array.isArray(value) ? value[0] : value;
}
function safeCompare(leftValue: string, rightValue: string): boolean {
const left = Buffer.from(leftValue);
const right = Buffer.from(rightValue);
return left.length === right.length && crypto.timingSafeEqual(left, right);
}
export function signGitHubBody(
rawBody: Buffer | string,
secret = WEBHOOK_SECRET
): string {
return (
"sha256=" +
crypto.createHmac("sha256", secret).update(rawBody).digest("hex")
);
}
export function verifyGitHubSignature(
rawBody: Buffer,
signatureHeader: string | undefined,
secret = WEBHOOK_SECRET
): boolean {
if (!signatureHeader?.startsWith("sha256=")) return false;
return safeCompare(signGitHubBody(rawBody, secret), signatureHeader);
}
function headersForStorage(req: express.Request): Record<string, string> {
const result: Record<string, string> = {};
for (const [key, value] of Object.entries(req.headers)) {
if (typeof value === "string") result[key] = value;
}
return result;
}
app.post("/webhooks/github", (req, res) => {
const rawBody = Buffer.isBuffer(req.body) ? req.body : Buffer.from("");
const signature = firstHeader(req.headers["x-hub-signature-256"]);
const deliveryId = firstHeader(req.headers["x-github-delivery"]);
const eventType = firstHeader(req.headers["x-github-event"]) ?? "unknown";
if (!verifyGitHubSignature(rawBody, signature)) {
return res.status(401).json({ error: "invalid_signature" });
}
if (!deliveryId) {
return res.status(400).json({ error: "missing_delivery_id" });
}
const id = `github:${deliveryId}`;
if (processedEvents.has(id) || eventStore.has(id)) {
return res.status(202).json({ id, status: "duplicate" });
}
let payload: unknown;
try {
payload = JSON.parse(rawBody.toString("utf8"));
} catch {
return res.status(400).json({ error: "invalid_json" });
}
eventStore.set(id, {
id,
provider: "github",
type: eventType,
headers: headersForStorage(req),
rawBody,
payload,
receivedAt: new Date().toISOString(),
status: "queued",
attempts: 0,
});
retryQueue.push(id);
void processNextEvent();
return res.status(202).json({ id, status: "queued" });
});
export async function processNextEvent(): Promise<void> {
const id = retryQueue.shift();
if (!id) return;
const event = eventStore.get(id);
if (!event || event.status === "processed") return;
event.status = "processing";
event.attempts += 1;
try {
await handleWebhookEvent(event);
event.status = "processed";
processedEvents.add(id);
} catch (error) {
event.status = "failed";
event.lastError = error instanceof Error ? error.message : String(error);
if (event.attempts < 5) {
const delayMs = Math.min(30_000, 1_000 * 2 ** event.attempts);
setTimeout(() => {
event.status = "queued";
retryQueue.push(id);
void processNextEvent();
}, delayMs);
}
}
}
async function handleWebhookEvent(event: WebhookEvent): Promise<void> {
if (event.type === "push") console.log("GitHub push received", event.id);
}
if (process.env.NODE_ENV !== "test") {
const port = Number(process.env.PORT ?? 3000);
app.listen(port, () => {
console.log(`Webhook server listening on http://127.0.0.1:${port}`);
});
}
WEBHOOK_SECRET=dev-secret-change-me npx tsx src/server.ts
Local sender और tests
scripts/send-local-webhook.ts:
import crypto from "node:crypto";
const secret = process.env.WEBHOOK_SECRET ?? "dev-secret-change-me";
const url =
process.env.WEBHOOK_URL ?? "http://127.0.0.1:3000/webhooks/github";
const body = JSON.stringify({ ref: "refs/heads/main", after: "local-test" });
const signature =
"sha256=" + crypto.createHmac("sha256", secret).update(body).digest("hex");
const response = await fetch(url, {
method: "POST",
headers: {
"content-type": "application/json",
"x-github-event": "push",
"x-github-delivery": `local-${Date.now()}`,
"x-hub-signature-256": signature,
},
body,
});
console.log(response.status, await response.text());
WEBHOOK_SECRET=dev-secret-change-me npx tsx scripts/send-local-webhook.ts
NODE_ENV=test npx tsx --test test/webhook.test.ts
Replay के लिए url, headers और body को बिना reformat किए save करें। HMAC signature exact bytes पर बनती है, इसलिए नया newline भी verification fail करा सकता है।
Practical use cases
Payments में Stripe event order confirm कर सकता है, failed invoice पर access pause कर सकता है और receipt भेज सकता है। Development workflow में GitHub push और pull_request previews, docs और internal notification trigger करते हैं। Forms और CRM में external ID duplicate tickets रोकता है। अपने SaaS से outgoing webhook भेजते समय भी signed payload, delivery logs, timeout, retry और manual resend चाहिए।
Common pitfalls
पहला pitfall है JSON parse को signature verification से पहले चलाना। दूसरा है 2xx return करने से पहले heavy काम करना, जिससे provider retry करता है। तीसरा है हर request पर नया UUID बनाकर idempotency key समझ लेना। चौथा है original delivery save न करना और केवल logs पर निर्भर रहना। Secret या पूरा payload logs में न डालें।
Production checklist
- Webhook routes पर ही raw body preserve होता है।
- Invalid signature
401, invalid JSON400, accepted work202देता है। - Provider delivery ID idempotency key है।
- Event store raw body, headers, status, attempts और last error रखता है।
- Retry queue में limit, backoff और final failure alert है।
- Replay script saved delivery से काम करता है।
- Secrets environment variable या secret manager से आते हैं।
Summary
Production Webhook छोटा endpoint है, लेकिन उसके अंदर security, reliability और operations छिपे होते हैं। Claude Code से बेहतर output चाहिए तो prompt में provider contract, raw body, signature verification, idempotency, queue, tests, replay और runbook शुरू से दें।
ClaudeCodeLab के Products में reusable templates हैं, और team adoption के लिए Training उपलब्ध है। Practical test में raw body और idempotency tests पहले लिखने से Claude Code की बाद की corrections काफी कम हुईं।
मुफ़्त PDF: Claude Code cheatsheet
Email डालें और commands, review habits तथा safe workflow वाली एक-page PDF पाएँ.
हम आपका data सुरक्षित रखते हैं और spam नहीं भेजते.
लेखक के बारे में
Masa
Claude Code workflow और team adoption पर काम करने वाला engineer.
संबंधित लेख
Claude Code Permission Receipt Pattern: scope, proof और rollback लिखना
Claude Code के लिए permission receipt: allowed actions, approval boundary, verification commands, rollback note और revenue CTA checks।
Claude Code और Codex के लिए सुरक्षित Agent Harness: permissions, verification और rollback
Claude Code और Codex agents के लिए सुरक्षित harness: permissions, plan, verification और rollback.
Claude Code Subagents गाइड: article और code work को सुरक्षित तरीके से delegate करें
Claude Code subagents से article और code work बांटें: delegation rules, prompts, pitfalls, checklist और examples.