Loading...
Loading...
Use when implementing Stripe webhook endpoints and getting 'Raw body not available' or signature verification errors - provides raw body parsing solutions and subscription period field fixes across frameworks
npx skill4agent add pr-pm/prpm integrating-stripe-webhooksTypeError: Cannot read property 'current_period_start'| Problem | Solution |
|---|---|
| Raw body not available | Configure custom body parser (see framework examples) |
| Signature verification fails | Use raw body bytes/buffer, not parsed JSON |
| 404 on webhook endpoint | Register webhook route inside API prefix |
| Access from |
| URI validation errors | URL-encode dynamic parameters with |
constructEvent()// In main server file, BEFORE registering routes
server.addContentTypeParser('application/json',
{ parseAs: 'buffer' },
async (req: any, body: Buffer) => {
req.rawBody = body; // Store for webhooks
return JSON.parse(body.toString('utf8')); // Parse for other routes
}
);
// In webhook handler
const rawBody = (request as any).rawBody;
const event = stripe.webhooks.constructEvent(
rawBody, signature, webhookSecret
);// Define webhook route BEFORE express.json() middleware
app.post('/webhooks/stripe',
express.raw({ type: 'application/json' }),
(req, res) => {
const event = stripe.webhooks.constructEvent(
req.body, // Already raw Buffer
req.headers['stripe-signature'],
webhookSecret
);
}
);
app.use(express.json()); // After webhook route@app.post('/webhooks/stripe')
async def stripe_webhook(request: Request):
payload = await request.body() # Use .body() not .json()
signature = request.headers.get('stripe-signature')
event = stripe.Webhook.construct_event(
payload, signature, webhook_secret
)TypeError: Cannot read property 'current_period_start' of undefinedsubscription.items.data[0]SubscriptionItem// ❌ WRONG - fields don't exist here
new Date(subscription.current_period_start * 1000)
// ✅ CORRECT - get from first subscription item
const firstItem = subscription.items.data[0] as any;
const periodStart = firstItem?.current_period_start || subscription.billing_cycle_anchor;
const periodEnd = firstItem?.current_period_end || subscription.billing_cycle_anchor;
await updateOrg({
start_date: new Date(periodStart * 1000),
end_date: new Date(periodEnd * 1000),
});// ❌ WRONG - creates /webhooks/stripe instead of /api/v1/webhooks/stripe
export async function registerRoutes(server) {
server.register(async (api) => {
await api.register(subscriptionRoutes, { prefix: '/subscriptions' });
}, { prefix: '/api/v1' });
await server.register(webhookRoutes, { prefix: '/webhooks' }); // Outside!
}
// ✅ CORRECT - inside API prefix
export async function registerRoutes(server) {
server.register(async (api) => {
await api.register(subscriptionRoutes, { prefix: '/subscriptions' });
await api.register(webhookRoutes, { prefix: '/webhooks' }); // Inside
}, { prefix: '/api/v1' });
}"body/successUrl must match format 'uri'"// ❌ WRONG - "Broke Org" creates invalid URL
const successUrl = `${origin}/orgs?name=${orgName}&subscription=success`;
// ✅ CORRECT - encode dynamic parameters
const successUrl = `${origin}/orgs?name=${encodeURIComponent(orgName)}&subscription=success`;STRIPE_WEBHOOK_SECRETstripe-signaturestripe.webhooks.constructEvent()SignatureVerificationErrorsubscription.items.data[0]anybilling_cycle_anchororg_id# Install Stripe CLI
brew install stripe/stripe-cli/stripe
# Forward webhooks to local server
stripe listen --forward-to localhost:3000/api/v1/webhooks/stripe
# Trigger test events
stripe trigger customer.subscription.created
stripe trigger customer.subscription.updated
stripe trigger invoice.paid