Back to roadmaps paddle Course

Processing Subscription Lifecycle Events

Once a webhook signature is verified, parse the event payload and update your local database to manage user premium access.


1. Essential Webhook Event Types

To handle subscriptions, monitor these event notifications:

  • subscription.created: Sent when a user completes their initial subscription payment.
  • subscription.updated: Sent when a plan is upgraded, downgraded, or a payment cycle renews.
  • subscription.canceled: Sent when the subscription is cancelled and premium access is terminated.

2. Implementing the Event Handler Action

Update your API webhook route to parse the events and write data using Prisma:

// app/api/webhooks/paddle/route.ts (Continued)
import { prisma } from "../../../lib/prisma";

export async function processVerifiedEvent(event: any) {
  const eventType = event.event_type;
  const data = event.data;

  switch (eventType) {
    case "subscription.created": {
      const customerId = data.customerId;
      const subscriptionId = data.id;
      const priceId = data.items[0].priceId;
      
      // Look up custom client details attached to the transaction
      const userId = data.customData?.userId;

      if (userId) {
        // Link subscription to user account and upgrade status
        await prisma.user.update({
          where: { id: userId },
          data: {
            paddleCustomerId: customerId,
            paddleSubscriptionId: subscriptionId,
            pricingTier: "pro", // Set role tier
            subscriptionActive: true,
          },
        });
      }
      break;
    }

    case "subscription.updated": {
      const subscriptionId = data.id;
      const status = data.status; // Options: active, trialing, past_due, paused

      const isSessionActive = status === "active" || status === "trialing";

      await prisma.user.updateMany({
        where: { paddleSubscriptionId: subscriptionId },
        data: {
          subscriptionActive: isSessionActive,
        },
      });
      break;
    }

    case "subscription.canceled": {
      const subscriptionId = data.id;

      // Revoke user premium access
      await prisma.user.updateMany({
        where: { paddleSubscriptionId: subscriptionId },
        data: {
          pricingTier: "free",
          subscriptionActive: false,
        },
      });
      break;
    }

    default:
      console.log(`Unhandled Paddle event category: ${eventType}`);
  }
}

3. Important Design Advice

  • Idempotency: Webhook events can occasionally be delivered twice. Ensure your database operations update records using unique keys (such as paddleSubscriptionId) to avoid duplicate row creation.
  • Metadata Pass-Through: When opening checkout windows in the client, always attach userId inside the customData payload. This guarantees you can associate webhook payloads back to specific accounts.
Published on Last updated: