Webhook Events

The boilerplate provides comprehensive webhook event processing that works with both payment systems: Better Auth handles standard events automatically, while custom processing handles advanced features like guest checkout.
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
Endpoint: /api/auth/stripe/webhook

Webhook 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.created
  • customer.subscription.updated
  • customer.subscription.deleted
  • customer.subscription.paused
  • customer.subscription.resumed
Automatic Actions:
  • 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.
The boilerplate includes sophisticated custom event processing for advanced checkout scenarios that Better Auth doesn't handle 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
}
Guest Checkout Workflow:
  1. Detection: Identifies guest checkout by metadata (referenceId: 'guest' or guest_checkout: 'true')
  2. Validation: Extracts customer data from Stripe session
  3. User Creation: Creates user account automatically from checkout data
  4. 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)
}
Additional events can also be handled in your custom event handler:

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,
  })
  break

Trial 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),
  })
  break

Usage-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),
  })
  break

Event-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
}
Subscription Updated:
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
}
Subscription Canceled:
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
Failed Payments:
  • 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
})
Security Features:
  • 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)
Log Example:
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/webhook
2

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_failed
3

Verify Event Processing

  1. Check application logs for event processing
  2. Verify database updates occurred
  3. Confirm email notifications sent
  4. Test custom business logic execution

Production Webhook Testing

1

Webhook Endpoint Verification

  1. Verify webhook endpoint is accessible
  2. Test signature verification works
  3. Confirm SSL certificate validity
  4. Check response times and reliability
2

Event Processing Validation

  1. Process real Stripe events
  2. Monitor webhook delivery success rates
  3. Verify all events are being handled
  4. Test error handling and recovery
3

Business Logic Verification

  1. Create test subscriptions and payments
  2. Verify all business workflows execute correctly
  3. Test edge cases and error scenarios
  4. 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:
# macOS
brew install stripe/stripe-cli/stripe

# Windows
scoop install stripe

# Linux - download from GitHub releases
# https://github.com/stripe/stripe-cli/releases
Login to your Stripe account:
stripe login
2

Start Webhook Listener

The boilerplate includes a pre-configured command to forward webhooks to your local development server:
pnpm stripe:listen
This runs: stripe 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:
# Output example:
> Ready! Your webhook signing secret is whsec_1a2b3c...
Add this to your .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.
Script Features:
  • 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
Menu Options:
  1. Subscription Created - checkout.session.completed
  2. Subscription Renewed - customer.subscription.updated
  3. Subscription Canceled - customer.subscription.deleted
  4. Run All Tests - Execute all tests in sequence
  5. Debug - Check existing subscriptions

Development Workflow

Efficient webhook testing workflow:
1

Setup Development Environment

  1. Start your Next.js development server: pnpm dev
  2. Start the Stripe webhook listener: pnpm stripe:listen
  3. Update your webhook secret in .env.local
2

Create Test Data

  1. Complete a checkout flow in your application
  2. Note the subscription UUID created in your database
  3. 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
Monitor the results:
  • 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.deleted

Testing 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.
    Webhook Events | ShipSaaS Documentation | ShipSaaS - Launch your SaaS with AI in days