Implement secure webhook handlers in Google Antigravity with signature verification and idempotency.
# Secure Webhook Handlers for Google Antigravity
Implement secure, reliable webhook handlers with proper signature verification and idempotency.
## Webhook Handler Base Class
```typescript
// lib/webhooks/base-handler.ts
import { NextRequest, NextResponse } from "next/server";
import crypto from "crypto";
export interface WebhookConfig {
secret: string;
signatureHeader: string;
timestampHeader?: string;
tolerance?: number;
}
export abstract class WebhookHandler {
protected config: WebhookConfig;
constructor(config: WebhookConfig) {
this.config = config;
}
protected verifySignature(payload: string, signature: string, timestamp?: string): boolean {
const { secret, tolerance = 300 } = this.config;
if (timestamp) {
const now = Math.floor(Date.now() / 1000);
if (Math.abs(now - parseInt(timestamp)) > tolerance) throw new Error("Timestamp too old");
}
const signedPayload = timestamp ? `${timestamp}.${payload}` : payload;
const expected = crypto.createHmac("sha256", secret).update(signedPayload).digest("hex");
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}
async handle(request: NextRequest): Promise<NextResponse> {
const body = await request.text();
const signature = request.headers.get(this.config.signatureHeader);
if (!signature) return NextResponse.json({ error: "Missing signature" }, { status: 401 });
try {
if (!this.verifySignature(body, signature)) {
return NextResponse.json({ error: "Invalid signature" }, { status: 401 });
}
await this.processEvent(JSON.parse(body));
return NextResponse.json({ received: true });
} catch (error) {
console.error("Webhook error:", error);
return NextResponse.json({ error: "Processing failed" }, { status: 500 });
}
}
protected abstract processEvent(event: unknown): Promise<void>;
}
```
## Idempotency Guard
```typescript
// lib/webhooks/idempotency.ts
import { createClient } from "@/lib/supabase/server";
export class IdempotencyGuard {
private supabase = createClient();
async isProcessed(eventId: string): Promise<boolean> {
const { data } = await this.supabase.from("webhook_events").select("id").eq("event_id", eventId).single();
return !!data;
}
async markProcessed(eventId: string, eventType: string, status: "success" | "failed"): Promise<void> {
await this.supabase.from("webhook_events").insert({ event_id: eventId, event_type: eventType, processed_at: new Date().toISOString(), status });
}
async withIdempotency<T>(eventId: string, eventType: string, processor: () => Promise<T>): Promise<T | null> {
if (await this.isProcessed(eventId)) return null;
try {
const result = await processor();
await this.markProcessed(eventId, eventType, "success");
return result;
} catch (error) {
await this.markProcessed(eventId, eventType, "failed");
throw error;
}
}
}
```
## Stripe Webhook Example
```typescript
// app/api/webhooks/stripe/route.ts
import { NextRequest, NextResponse } from "next/server";
import Stripe from "stripe";
import { stripe } from "@/lib/stripe";
import { createClient } from "@/lib/supabase/admin";
import { IdempotencyGuard } from "@/lib/webhooks/idempotency";
const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET!;
const idempotency = new IdempotencyGuard();
export async function POST(request: NextRequest) {
const body = await request.text();
const signature = request.headers.get("stripe-signature")!;
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(body, signature, webhookSecret);
} catch (err) {
return NextResponse.json({ error: "Invalid signature" }, { status: 400 });
}
await idempotency.withIdempotency(event.id, event.type, async () => {
const supabase = createClient();
switch (event.type) {
case "checkout.session.completed": {
const session = event.data.object as Stripe.Checkout.Session;
await supabase.from("profiles").update({ subscription_status: "active" }).eq("stripe_customer_id", session.customer);
break;
}
}
});
return NextResponse.json({ received: true });
}
```
## Webhook Queue
```typescript
// lib/webhooks/queue.ts
import { createClient } from "@/lib/supabase/server";
export class WebhookQueue {
private supabase = createClient();
async enqueue(eventType: string, payload: Record<string, unknown>): Promise<string> {
const { data } = await this.supabase.from("webhook_queue").insert({ event_type: eventType, payload, attempts: 0, status: "pending" }).select("id").single();
return data!.id;
}
async processNext(handler: (webhook: any) => Promise<void>): Promise<boolean> {
const { data: webhook } = await this.supabase.from("webhook_queue").select("*").eq("status", "pending").order("created_at").limit(1).single();
if (!webhook) return false;
await this.supabase.from("webhook_queue").update({ status: "processing" }).eq("id", webhook.id);
try {
await handler(webhook);
await this.supabase.from("webhook_queue").update({ status: "completed" }).eq("id", webhook.id);
} catch (error) {
const nextRetry = new Date(Date.now() + Math.pow(2, webhook.attempts + 1) * 60000).toISOString();
await this.supabase.from("webhook_queue").update({ status: "pending", attempts: webhook.attempts + 1, next_retry_at: nextRetry }).eq("id", webhook.id);
}
return true;
}
}
```
## Best Practices
1. **Signature Verification**: Always verify webhook signatures
2. **Idempotency**: Use event IDs to prevent duplicate processing
3. **Async Processing**: Queue webhooks for reliable background processing
4. **Retry Logic**: Implement exponential backoff for failures
5. **Monitoring**: Log all webhook events for debuggingThis webhooks prompt is ideal for developers working on:
By using this prompt, you can save hours of manual coding and ensure best practices are followed from the start. It's particularly valuable for teams looking to maintain consistency across their webhooks implementations.
Yes! All prompts on Antigravity AI Directory are free to use for both personal and commercial projects. No attribution required, though it's always appreciated.
This prompt works excellently with Claude, ChatGPT, Cursor, GitHub Copilot, and other modern AI coding assistants. For best results, use models with large context windows.
You can modify the prompt by adding specific requirements, constraints, or preferences. For webhooks projects, consider mentioning your framework version, coding style, and any specific libraries you're using.