EZSalonEZSalon
  • Blog
  • Changelog
  • Documentation
  • Pricing
  • FAQ
EZSalonEZSalon

Spend less time managing your salon, and more time growing it. Built for nail and beauty salon owners, on web, iOS and Android.

© Copyright 2026 EZSalon.io. All Rights Reserved. v4.1.5

About
  • Blog
  • Contact
Product
  • Documentation
Legal
  • Terms of Service
  • Privacy Policy
  • Cookie Policy
  • Blog
  • Changelog
  • Documentation
  • Pricing
  • FAQ
    • Introduction
      • Installing Dependencies
      • Quick Start
      • Project Structure
      • Configuration
    • Authentication Overview
      • Configuration
      • Email & Password
      • OAuth
      • Magic Links
    • Database
      • Database Overview
      • Migrations
      • Row Level Security
      • Querying Data
      • Functions & Triggers
    • Features
      • Features Overview
      • Team Collaboration
      • File Uploads
    • Billing & Payments
      • Billing Overview
      • Pricing Plans
      • Webhook Integration

    Webhook Integration

    Setting up and handling payment provider webhooks for subscription events.

    Note: This is mock/placeholder content for demonstration purposes.

    Webhooks notify your application when billing events occur, ensuring your app stays synchronized with your payment provider.

    Why Webhooks?

    Webhooks are essential for:

    • Real-time updates - Instant notification of payment events
    • Reliability - Handles events even if users close their browser
    • Security - Server-to-server communication
    • Automation - Automatic subscription status updates

    Webhook Endpoint

    Your webhook endpoint receives events from the payment provider:

    // app/api/billing/webhook/route.ts
    export async function POST(request: Request) {
      const body = await request.text();
      const signature = request.headers.get('stripe-signature');
    
      // Verify webhook signature
      const event = stripe.webhooks.constructEvent(
        body,
        signature,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    
      // Handle the event
      await handleBillingEvent(event);
    
      return new Response('OK', { status: 200 });
    }
    

    Common Events

    Subscription Created

    case 'customer.subscription.created':
      await prisma.subscription.create({
        data: {
          id: event.data.object.id,
          accountId: event.data.object.metadata.accountId,
          status: 'active',
          planId: event.data.object.items.data[0].price.id,
          currentPeriodEnd: new Date(event.data.object.current_period_end * 1000),
        },
      });
      break;
    

    Subscription Updated

    case 'customer.subscription.updated':
      await prisma.subscription.update({
        where: { id: event.data.object.id },
        data: {
          status: event.data.object.status,
          planId: event.data.object.items.data[0].price.id,
          currentPeriodEnd: new Date(event.data.object.current_period_end * 1000),
        },
      });
      break;
    

    Subscription Deleted

    case 'customer.subscription.deleted':
      await prisma.subscription.update({
        where: { id: event.data.object.id },
        data: {
          status: 'canceled',
          canceledAt: new Date(),
        },
      });
      break;
    

    Payment Failed

    case 'invoice.payment_failed':
      const subscription = await prisma.subscription.findUnique({
        where: { id: event.data.object.subscription },
      });
    
      // Send payment failure notification
      await sendPaymentFailureEmail(subscription.accountId);
      break;
    

    Setting Up Webhooks

    Stripe

    1. Local Development (using Stripe CLI):
    stripe listen --forward-to localhost:3000/api/billing/webhook
    
    1. Production:
    • Go to Stripe Dashboard → Developers → Webhooks
    • Add endpoint: https://yourdomain.com/api/billing/webhook
    • Select events to listen to
    • Copy webhook signing secret to your .env

    Paddle

    1. Configure webhook URL in Paddle dashboard
    2. Add webhook secret to environment variables
    3. Verify webhook signature:
    const signature = request.headers.get('paddle-signature');
    const verified = paddle.webhooks.verify(body, signature);
    
    if (!verified) {
      return new Response('Invalid signature', { status: 401 });
    }
    

    Security Best Practices

    1. Always verify signatures - Prevents unauthorized requests
    2. Use HTTPS - Encrypts webhook data in transit
    3. Validate event data - Check for required fields
    4. Handle idempotently - Process duplicate events safely
    5. Return 200 quickly - Acknowledge receipt, process async

    Error Handling

    async function handleBillingEvent(event: Event) {
      try {
        await processEvent(event);
      } catch (error) {
        // Log error for debugging
        console.error('Webhook error:', error);
    
        // Store failed event for retry
        await prisma.failedWebhook.create({
          data: {
            eventId: event.id,
            type: event.type,
            payload: event,
            error: error.message,
          },
        });
    
        // Throw to trigger provider retry
        throw error;
      }
    }
    

    Testing Webhooks

    Using Provider's CLI Tools

    # Stripe
    stripe trigger customer.subscription.created
    
    # Test specific scenarios
    stripe trigger payment_intent.payment_failed
    

    Manual Testing

    curl -X POST https://your-app.com/api/billing/webhook \
      -H "Content-Type: application/json" \
      -H "stripe-signature: test_signature" \
      -d @test-event.json
    

    Monitoring

    Track webhook delivery:

    • Response times
    • Success/failure rates
    • Event processing duration
    • Failed events requiring manual intervention

    Most providers offer webhook monitoring dashboards showing delivery attempts and failures.