
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."
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."
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
You Might Also Like
Related Skills

gog
Google Workspace CLI for Gmail, Calendar, Drive, Contacts, Sheets, and Docs.
openclaw
orpc-contract-first
Guide for implementing oRPC contract-first API patterns in Dify frontend. Triggers when creating new API contracts, adding service endpoints, integrating TanStack Query with typed contracts, or migrating legacy service calls to oRPC. Use for all API layer work in web/contract and web/service directories.
langgenius
