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.