Back to roadmaps paddle Course

Project: One-Time Purchase and License Key Delivery

In this project, we will build a one-time purchase billing workflow. When a user buys our software application, we will verify the purchase webhook, generate a license key, save it to PostgreSQL, and email it using the Resend API.


1. Lifecycle Event Ingestion

graph TD
    A[Customer pays once on checkout] --> B[Paddle triggers transaction.completed]
    B --> C[Server generates a License Key]
    C --> D[Save Key in Database]
    D --> E[Email Key to Customer via Resend]

2. Webhook Event Processing Action

In your Webhook route, monitor the transaction.completed event:

// app/api/webhooks/paddle/route.ts (One-time transaction snippet)
import { prisma } from "../../../lib/prisma";
import { resend } from "../../../lib/resend";
import crypto from "crypto";

export async function handleTransactionComplete(event: any) {
  const data = event.data;

  // 1. Double check the transaction is fully paid and billed
  if (data.status !== "completed") return;

  const email = data.customer?.email || data.billingDetails?.email;
  const transactionId = data.id;

  if (!email) {
    console.error("Missing customer email details in transaction:", transactionId);
    return;
  }

  // 2. Prevent duplicate code processing by checking if this transaction already generated keys
  const existingKey = await prisma.licenseKey.findUnique({
    where: { transactionId },
  });

  if (existingKey) {
    console.log("Transaction already processed keys:", transactionId);
    return;
  }

  // 3. Generate a secure custom license key
  const licenseToken = `LIC-${crypto.randomBytes(16).toString("hex").toUpperCase()}`;

  // 4. Save license state in database
  await prisma.licenseKey.create({
    data: {
      email,
      keyString: licenseToken,
      transactionId,
      activated: false,
    },
  });

  // 5. Deliver registration key to recipient inbox via Resend
  try {
    await resend.emails.send({
      from: "Licensing Desk <desk@mycompany.com>",
      to: [email],
      subject: "Your Software License Activation Key",
      html: `
        <h2>Purchase Confirmation</h2>
        <p>Thank you for buying our application! Here is your private activation code:</p>
        <pre style="background:#f3f4f6; padding:15px; border-radius:8px; font-weight:bold;">${licenseToken}</pre>
        <p>Enter this registration key in the application settings page to unlock all pro features.</p>
      `,
    });
    console.log("License email successfully sent to:", email);
  } catch (err: any) {
    console.error("Failed to email licensing code:", err.message);
  }
}

3. Database Model Requirement

Create this database model to store your app registration keys:

// schema.prisma snippet
model LicenseKey {
  id            String   @id @default(cuid())
  email         String
  keyString     String   @unique
  transactionId String   @unique
  activated     Boolean  @default(false)
  activatedAt   DateTime?
  createdAt     DateTime @default(now())
}
Published on Last updated: