Hybrid System: Better Auth processes standard subscription events automatically, while custom event handlers manage advanced features like guest checkout and installments from the Custom Boilerplate System.
Perfect for: Real-time payment processing, automatic subscription updates, and reliable event-driven business logic with robust error handling.
Webhook Architecture
The webhook system uses a unified approach that serves both payment systems:Important: Both Better Auth and Custom Boilerplate systems use the same webhook endpoint (
/api/auth/stripe/webhook) but events are processed differently based on their source and metadata.Automatic Handling:
- Customer creation and updates
- Subscription lifecycle events
- Payment confirmations
- Database synchronization
- User notification triggers
/api/auth/stripe/webhookWebhook Configuration
Better Auth Plugin Setup
The webhook endpoint is automatically configured through the Better Auth Stripe plugin:// From src/lib/better-auth/auth.ts
stripe({
stripeClient,
stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET,
createCustomerOnSignUp: true,
subscription: {
enabled: true,
// Automatic event handlers...
onSubscriptionComplete: async ({ subscription }) => { /* ... */ },
onSubscriptionUpdate: async ({ subscription }) => { /* ... */ },
onSubscriptionCancel: async ({ subscription }) => { /* ... */ },
onSubscriptionDeleted: async ({ subscription }) => { /* ... */ },
},
onEvent: onStripeEvent, // Custom event handler
})Custom Event Handler
Additional business logic is processed in the custom event handler:// From src/lib/stripe/stripe-events.ts
export const onStripeEvent = async (event: Stripe.Event) => {
console.log(`Processing Stripe event: ${event.type}`)
// Custom business logic for events not handled by Better Auth
switch (event.type) {
case 'invoice.payment_failed':
// Handle failed payments
break
case 'customer.subscription.trial_will_end':
// Send trial ending notifications
break
default:
// Log unhandled events
console.log(`Unhandled event type: ${event.type}`)
}
}Processed Events
Automatically Handled by Better Auth
Events Processed:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedcustomer.subscription.pausedcustomer.subscription.resumed
- Database subscription updates
- User access level changes
- Email notifications sent
- Usage limits updated
- Billing status synchronized
Custom Event Processing (Custom System Features)
Custom Boilerplate System Only: The following advanced event processing is specific to the Custom Boilerplate System's features like guest checkout and installments. Better Auth handles its events automatically.
Guest Checkout Processing (Custom System)
Advanced Guest Checkout with Account Creation:// From src/lib/stripe/stripe-events.ts
case 'checkout.session.completed': {
const session = event.data.object as Stripe.Checkout.Session
const isCustomCheckout = session.metadata?.source === 'custom_checkout'
// Handle guest checkout with automatic user creation
if (isCustomCheckout) {
await handleGuestCheckoutSessionCompleted(session)
}
// Handle installment checkout
const isInstallmentCheckout = session.metadata?.source === 'installment_checkout'
if (isInstallmentCheckout) {
await handleInstallmentCheckoutSessionCompleted(session)
}
break
}- Detection: Identifies guest checkout by metadata (
referenceId: 'guest'orguest_checkout: 'true') - Validation: Extracts customer data from Stripe session
- User Creation: Creates user account automatically from checkout data
- Subscription Linking: Links new user to their paid subscription
Installment Checkout Processing (Custom System)
Payment Installments Support:// From src/lib/stripe/stripe-events.ts
async function handleInstallmentCheckoutSessionCompleted(session: Stripe.Checkout.Session) {
// 1. Validate installment data (schedule_id, number_of_payments)
const installmentData = validateInstallmentCheckoutData(session, metadata)
// 2. Handle user creation (guest or authenticated)
const finalUser = isGuestCheckout
? await createGuestUser(customerData)
: await getAuthenticatedUser(customerEmail)
// 3. Create subscription with installment periods
await createInstallmentSubscription(installmentData, finalUser)
}Payment Failure Handling
// Custom handling for payment failures
case 'invoice.payment_failed':
const invoice = event.data.object as Stripe.Invoice
// Custom business logic
await handlePaymentFailure({
customerId: invoice.customer as string,
invoiceId: invoice.id,
amountDue: invoice.amount_due,
attemptCount: invoice.attempt_count,
})
breakTrial Expiration Notifications
// Custom trial ending notifications
case 'customer.subscription.trial_will_end':
const subscription = event.data.object as Stripe.Subscription
await sendTrialExpirationNotification({
customerId: subscription.customer as string,
subscriptionId: subscription.id,
trialEnd: new Date(subscription.trial_end! * 1000),
})
breakUsage-Based Billing Events
// Handle metered billing events
case 'invoice.upcoming':
const upcomingInvoice = event.data.object as Stripe.Invoice
// Calculate usage charges before invoice finalization
await calculateUsageCharges({
customerId: upcomingInvoice.customer as string,
periodStart: new Date(upcomingInvoice.period_start * 1000),
periodEnd: new Date(upcomingInvoice.period_end * 1000),
})
breakEvent-Driven Business Logic
Subscription Lifecycle Management
Subscription Created:onSubscriptionComplete: async ({ subscription }) => {
// Automatic Better Auth processing:
// 1. Update user's subscription status in database
// 2. Send welcome email notification
// 3. Grant access to premium features
// 4. Update usage limits
// 5. Log subscription event for analytics
}onSubscriptionUpdate: async ({ subscription }) => {
// Automatic Better Auth processing:
// 1. Update subscription details in database
// 2. Adjust user access levels
// 3. Send plan change confirmation email
// 4. Update feature limitations
// 5. Prorate billing if necessary
}onSubscriptionCancel: async ({ subscription }) => {
// Automatic Better Auth processing:
// 1. Mark subscription as canceled
// 2. Send cancellation confirmation
// 3. Schedule access removal (if immediate)
// 4. Trigger win-back campaign
// 5. Update customer success metrics
}Payment Processing Workflows
Successful Payments:- Update payment status
- Send receipt email
- Renew service access
- Update billing history
- Trigger analytics events
- Initiate retry logic
- Send payment failure notifications
- Apply grace period policies
- Schedule account restrictions
- Log failure reasons for analysis
Webhook Security
Signature Verification
The Better Auth plugin automatically verifies webhook signatures:// Automatic signature verification by Better Auth
stripe({
stripeWebhookSecret: env.STRIPE_WEBHOOK_SECRET, // Used for verification
})- HMAC-SHA256 signature validation
- Timestamp tolerance checking
- Replay attack prevention
- Invalid signature rejection
- Secure secret management
Error Handling
Webhook Reliability:- Automatic retry logic for failed webhooks
- Error logging and monitoring
- Dead letter queue for persistent failures
- Alert system for webhook issues
- Performance monitoring and optimization
Event Monitoring & Logging
Event Processing Logs
Log Information:- Event type and ID
- Processing timestamp
- Success/failure status
- Processing duration
- Error details (if any)
export const onStripeEvent = async (event: Stripe.Event) => {
console.log(`Processing Stripe event: ${event.type}`)
console.log(`Event ID: ${event.id}`)
console.log(`Created: ${new Date(event.created * 1000).toISOString()}`)
try {
// Process event
await processCustomEvent(event)
console.log(`✅ Successfully processed ${event.type}`)
} catch (error) {
console.error(`❌ Error processing ${event.type}:`, error)
throw error // Let Better Auth handle retry logic
}
}Performance Monitoring
Metrics Tracking:- Webhook processing times
- Success/failure rates
- Event volume trends
- Error frequency analysis
- Performance bottlenecks
Testing Webhooks
Local Development Testing
1
Set Up Stripe CLI
Install and configure Stripe CLI for local webhook forwarding:
# Install Stripe CLI
# Forward webhooks to local development server
stripe listen --forward-to localhost:3000/api/auth/stripe/webhook2
Trigger Test Events
Use Stripe CLI to trigger test events:
# Test subscription creation
stripe trigger customer.subscription.created
# Test payment success
stripe trigger invoice.payment_succeeded
# Test payment failure
stripe trigger invoice.payment_failed3
Verify Event Processing
- Check application logs for event processing
- Verify database updates occurred
- Confirm email notifications sent
- Test custom business logic execution
Production Webhook Testing
1
Webhook Endpoint Verification
- Verify webhook endpoint is accessible
- Test signature verification works
- Confirm SSL certificate validity
- Check response times and reliability
2
Event Processing Validation
- Process real Stripe events
- Monitor webhook delivery success rates
- Verify all events are being handled
- Test error handling and recovery
3
Business Logic Verification
- Create test subscriptions and payments
- Verify all business workflows execute correctly
- Test edge cases and error scenarios
- Validate notification delivery
Webhook Best Practices
Reliability
Ensure webhook reliability:- Implement idempotency for event processing
- Handle duplicate events gracefully
- Use database transactions for consistency
- Implement proper error handling and logging
- Monitor webhook performance and success rates
Security
Maintain webhook security:- Always verify webhook signatures
- Use HTTPS endpoints only
- Implement proper authentication
- Log security events and anomalies
- Regularly rotate webhook secrets
Performance
Optimize webhook performance:- Process events asynchronously when possible
- Implement proper timeout handling
- Use connection pooling for database operations
- Monitor and optimize processing times
- Scale webhook processing as needed
Webhook Testing & Development
Local Development: The boilerplate includes comprehensive webhook testing tools for local development, allowing you to test subscription flows without real payments.
Stripe CLI Setup
The boilerplate uses Stripe CLI for local webhook development:1
Install Stripe CLI
Install the Stripe CLI for your platform:Login to your Stripe account:
# macOS
brew install stripe/stripe-cli/stripe
# Windows
scoop install stripe
# Linux - download from GitHub releases
# https://github.com/stripe/stripe-cli/releasesstripe login2
Start Webhook Listener
The boilerplate includes a pre-configured command to forward webhooks to your local development server:This runs:
pnpm stripe:listenstripe listen --forward-to localhost:3000/api/auth/stripe/webhookWhat it does:- Listens for events in your Stripe account
- Forwards them to your local webhook endpoint
- Provides webhook signing secrets for testing
- Shows real-time event logs in your terminal
3
Update Environment Variables
When you start the listener, Stripe CLI provides a webhook signing secret:Add this to your
# Output example:
> Ready! Your webhook signing secret is whsec_1a2b3c....env.local:STRIPE_WEBHOOK_SECRET=whsec_1a2b3c...Testing Script
The boilerplate includes a comprehensive webhook testing script at/test-stripe-webhooks.sh:
Important: Start the webhook listener (
pnpm stripe:listen) before running tests to receive the events.- Tests 3 essential subscription workflows
- Uses your actual Stripe Price IDs
- Provides interactive menu for selective testing
- Includes debug options for troubleshooting
# Make the script executable
chmod +x test-stripe-webhooks.sh
# Run the interactive testing script
./test-stripe-webhooks.sh- Subscription Created -
checkout.session.completed - Subscription Renewed -
customer.subscription.updated - Subscription Canceled -
customer.subscription.deleted - Run All Tests - Execute all tests in sequence
- Debug - Check existing subscriptions
Development Workflow
Efficient webhook testing workflow:1
Setup Development Environment
- Start your Next.js development server:
pnpm dev - Start the Stripe webhook listener:
pnpm stripe:listen - Update your webhook secret in
.env.local
2
Create Test Data
- Complete a checkout flow in your application
- Note the subscription UUID created in your database
- Update the testing script with this UUID
3
Test Webhook Events
# Run the interactive testing script
./test-stripe-webhooks.sh
# Or trigger individual events:
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deleted- Check your application logs
- Verify database changes
- Test user notifications
- Confirm UI updates
4
Debug Issues
Use the script's debug features:
- Option 5: Check existing subscriptions
- Monitor webhook endpoint logs
- Verify event processing in Better Auth
- Check database updates after each event
Manual Testing Commands
For quick manual testing without the script:# Test subscription creation
stripe trigger checkout.session.completed
# Test subscription update (renewal)
stripe trigger customer.subscription.updated
# Test subscription cancellation
stripe trigger customer.subscription.deletedTesting Checklist
Verify webhook functionality:- Webhook listener is running (
pnpm stripe:listen) - Webhook secret is correctly configured
- Test subscription creation updates database
- Test subscription renewal extends period
- Test subscription cancellation updates status
- User notifications are sent correctly
- UI reflects subscription status changes
- Error handling works for invalid events
- Webhook signature verification is working
- Database transactions are atomic
Local Testing Ready! You can now test complete subscription workflows locally without processing real payments, ensuring your webhook handling works perfectly before going live.
Webhook system ready! Your application now processes Stripe events in real-time with reliable business logic execution and comprehensive error handling.