Agent skill
stripe-sync-webhook
When the user wants to create webhook handlers for stripe-sync-engine. Also use when the user mentions "webhook endpoint," "processWebhook," "stripe webhook handler," "stripe events," or "real-time sync."
Install this agent skill to your Project
npx add-skill https://github.com/ashutoshpw/stripe-sync-engine/tree/main/skills/webhook
SKILL.md
Stripe Sync Engine Webhook Setup
You are an expert in setting up Stripe webhook handlers that use stripe-sync-engine. Your goal is to help users create webhook endpoints that automatically sync Stripe events to their PostgreSQL database.
Initial Assessment
Before proceeding, verify:
- Is stripe-sync-engine set up? (see setup skill)
- Are migrations completed? (see migrations skill)
- What framework are you using? (Next.js, Hono, Deno Fresh, etc.)
Framework-Specific Implementations
Next.js App Router
Create app/api/webhooks/stripe/route.ts:
import { NextResponse } from 'next/server';
import { stripeSync } from '@/lib/stripeSync';
export async function POST(request: Request) {
try {
const signature = request.headers.get('stripe-signature') ?? undefined;
const arrayBuffer = await request.arrayBuffer();
const payload = Buffer.from(arrayBuffer);
await stripeSync.processWebhook(payload, signature);
return NextResponse.json({ received: true });
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Webhook processing failed:', message);
return NextResponse.json({ error: message }, { status: 400 });
}
}
Next.js Pages Router
Create pages/api/webhooks/stripe.ts:
import type { NextApiRequest, NextApiResponse } from 'next';
import { stripeSync } from '@/lib/stripeSync';
import { buffer } from 'micro';
// Disable body parsing - we need the raw body for signature verification
export const config = {
api: {
bodyParser: false,
},
};
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
if (req.method !== 'POST') {
return res.status(405).json({ error: 'Method not allowed' });
}
try {
const signature = req.headers['stripe-signature'] as string | undefined;
const payload = await buffer(req);
await stripeSync.processWebhook(payload, signature);
return res.status(200).json({ received: true });
} catch (error) {
const message = error instanceof Error ? error.message : 'Unknown error';
console.error('Webhook processing failed:', message);
return res.status(400).json({ error: message });
}
}
Install micro for body parsing:
npm install micro
Hono
import { Hono } from 'hono';
import { StripeSync } from 'stripe-sync-engine';
const stripeSync = new StripeSync({
poolConfig: { connectionString: process.env.DATABASE_URL },
stripeSecretKey: process.env.STRIPE_SECRET_KEY!,
stripeWebhookSecret: process.env.STRIPE_WEBHOOK_SECRET!,
});
const app = new Hono();
app.post('/webhooks/stripe', async (c) => {
const signature = c.req.header('stripe-signature') ?? undefined;
const arrayBuffer = await c.req.raw.arrayBuffer();
const payload = Buffer.from(arrayBuffer);
await stripeSync.processWebhook(payload, signature);
return c.json({ received: true });
});
Deno Fresh
Create routes/api/webhooks/stripe.ts:
import { Handlers } from "$fresh/server.ts";
import { stripeSync } from "../../../utils/stripeSync.ts";
export const handler: Handlers = {
async POST(req) {
try {
const signature = req.headers.get("stripe-signature") ?? undefined;
const payload = await req.text();
await stripeSync.processWebhook(payload, signature);
return new Response(
JSON.stringify({ received: true }),
{ headers: { "Content-Type": "application/json" } }
);
} catch (error) {
const message = error instanceof Error ? error.message : "Unknown error";
console.error("Webhook processing failed:", message);
return new Response(
JSON.stringify({ error: message }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
},
};
Cloudflare Workers (Forwarding Pattern)
Cloudflare Workers can't connect directly to PostgreSQL. Use a forwarding pattern:
import { Hono } from 'hono';
import Stripe from 'stripe';
type Bindings = {
STRIPE_SECRET_KEY: string;
STRIPE_WEBHOOK_SECRET: string;
FORWARD_SYNC_URL: string; // URL of your sync service
};
const app = new Hono<{ Bindings: Bindings }>();
app.post('/webhooks/stripe', async (c) => {
const { STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, FORWARD_SYNC_URL } = c.env;
const stripe = new Stripe(STRIPE_SECRET_KEY, {
httpClient: Stripe.createFetchHttpClient(),
});
const payload = await c.req.text();
const signature = c.req.header('stripe-signature');
if (!signature) {
return c.json({ error: 'Missing stripe-signature header' }, 400);
}
// Verify signature
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(payload, signature, STRIPE_WEBHOOK_SECRET);
} catch (error) {
return c.json({ error: 'Invalid Stripe signature' }, 400);
}
// Forward to sync service
await fetch(FORWARD_SYNC_URL, {
method: 'POST',
headers: {
'content-type': 'application/json',
'stripe-event-id': event.id,
},
body: JSON.stringify(event),
});
return c.json({ received: true });
});
export default app;
Configuring Stripe Dashboard
- Go to Stripe Dashboard > Webhooks
- Click Add endpoint
- Enter your webhook URL:
- Development: Use Stripe CLI (see below)
- Production:
https://yourdomain.com/api/webhooks/stripe
- Select events to listen to (recommended: select "All events")
- Copy the Signing secret (
whsec_...) to your environment variables
Local Development with Stripe CLI
Install and set up Stripe CLI:
# macOS
brew install stripe/stripe-cli/stripe
# Login
stripe login
# Forward webhooks to your local server
stripe listen --forward-to localhost:3000/api/webhooks/stripe
# In another terminal, trigger test events
stripe trigger payment_intent.succeeded
stripe trigger customer.created
stripe trigger invoice.paid
The CLI will show you a temporary webhook secret to use for local testing.
Event Types Processed
stripe-sync-engine automatically handles these event types:
| Category | Events |
|---|---|
| Customers | customer.created, customer.updated, customer.deleted |
| Products | product.created, product.updated, product.deleted |
| Prices | price.created, price.updated, price.deleted |
| Subscriptions | customer.subscription.* events |
| Invoices | invoice.* events |
| Payments | payment_intent.*, charge.* events |
| Disputes | charge.dispute.* events |
| Refunds | charge.refund.* events |
Adding Custom Business Logic
You can add your own logic after sync completes:
export async function POST(request: Request) {
const signature = request.headers.get('stripe-signature') ?? undefined;
const payload = await request.arrayBuffer();
// Sync to database
await stripeSync.processWebhook(Buffer.from(payload), signature);
// Parse event for custom logic
const event = JSON.parse(new TextDecoder().decode(payload));
switch (event.type) {
case 'customer.subscription.created':
// Send welcome email, provision access, etc.
await handleNewSubscription(event.data.object);
break;
case 'invoice.payment_failed':
// Send dunning email
await handlePaymentFailure(event.data.object);
break;
}
return NextResponse.json({ received: true });
}
Troubleshooting
Signature Verification Failed
- Ensure
STRIPE_WEBHOOK_SECRETmatches the signing secret from Stripe Dashboard - For local testing, use the secret from
stripe listenoutput - Ensure you're passing the raw body, not parsed JSON
Webhook Not Receiving Events
- Check Stripe Dashboard > Webhooks for delivery attempts
- Verify your endpoint URL is publicly accessible
- Check server logs for errors
Timeout Errors
- stripe-sync-engine is designed to be fast, but large payloads may take longer
- Consider increasing your serverless function timeout
- For very high volume, consider queueing events
Related Skills
- setup: Install and configure stripe-sync-engine
- migrations: Create the database schema first
- troubleshooting: Debug webhook issues
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
stripe-sync-migrations
When the user wants to run database migrations for stripe-sync-engine. Also use when the user mentions "run migrations," "stripe schema," "create stripe tables," "database setup," or "stripe_migrations."
stripe-sync-troubleshooting
When the user is experiencing issues with stripe-sync-engine. Also use when the user mentions "not working," "webhook error," "signature failed," "connection error," "data not syncing," or "stripe sync broken."
stripe-sync-query
When the user wants to query synced Stripe data. Also use when the user mentions "query stripe data," "stripe tables," "select from stripe," "stripe analytics," or "stripe SQL."
stripe-sync-minimal
Complete guide for stripe-sync-engine in one skill. Use when the user wants to "sync stripe to database," "stripe-sync-engine," "stripe postgres sync," or needs a quick all-in-one reference.
stripe-sync-setup
When the user wants to set up stripe-sync-engine in their project. Also use when the user mentions "set up stripe-sync-engine," "install stripe sync," "configure stripe sync," "add stripe database sync," or "stripe to postgres."
stripe-sync-backfill
When the user wants to import historical Stripe data. Also use when the user mentions "backfill stripe data," "syncBackfill," "import stripe data," "sync existing data," or "historical sync."
Didn't find tool you were looking for?