
stripe-sync-webhook
by ashutoshpw
SKILL.md
name: stripe-sync-webhook description: 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
Score
Total Score
Based on repository quality metrics
SKILL.mdファイルが含まれている
ライセンスが設定されている
100文字以上の説明がある
GitHub Stars 100以上
1ヶ月以内に更新
10回以上フォークされている
オープンIssueが50未満
プログラミング言語が設定されている
1つ以上のタグが設定されている
Reviews
Reviews coming soon
