Skip to main content
This guide walks through integrating AuthForge with Stripe to automatically generate and deliver license keys when customers complete a purchase.

Prerequisites

  • An AuthForge account with an app created
  • An AuthForge Developer API key
  • A Stripe account with API keys
  • A Node.js backend (examples use Express.js)

One-time purchases

Flow overview

1. Create a Stripe Checkout session

const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);

app.post("/api/create-checkout", async (req, res) => {
  const session = await stripe.checkout.sessions.create({
    mode: "payment",
    payment_method_types: ["card"],
    line_items: [
      {
        price: "price_your_product_price_id",
        quantity: 1,
      },
    ],
    success_url: "https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}",
    cancel_url: "https://yoursite.com/pricing",
    metadata: {
      authforge_app_id: process.env.AUTHFORGE_APP_ID,
      license_duration: "365",      // days, or omit for lifetime
      max_hwid_slots: "1",
    },
  });

  res.json({ url: session.url });
});

2. Handle the Stripe webhook

const crypto = require("crypto");

app.post(
  "/webhooks/stripe",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const sig = req.headers["stripe-signature"];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      console.error("Webhook signature verification failed:", err.message);
      return res.sendStatus(400);
    }

    if (event.type === "checkout.session.completed") {
      const session = event.data.object;
      await handleCompletedCheckout(session);
    }

    res.sendStatus(200);
  }
);

3. Generate the license key

async function handleCompletedCheckout(session) {
  const { authforge_app_id, license_duration, max_hwid_slots } =
    session.metadata;

  // Idempotency: check if we've already created a license for this session
  const existingLicense = await db.getLicenseByStripeSession(session.id);
  if (existingLicense) {
    console.log("License already created for session", session.id);
    return;
  }

  // Calculate expiration
  const expiresAt = license_duration
    ? new Date(Date.now() + parseInt(license_duration) * 86400000).toISOString()
    : null;

  // Create license via AuthForge API
  const response = await fetch("https://api.authforge.cc/v1/licenses", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      appId: authforge_app_id,
      quantity: 1,
      expiresAt,
      maxHwidSlots: parseInt(max_hwid_slots) || 1,
      label: `Stripe session ${session.id}`,
    }),
  });

  const data = await response.json();
  const licenseKey = data.licenses[0].licenseKey;

  // Store the mapping in your database
  await db.saveLicense({
    stripeSessionId: session.id,
    customerEmail: session.customer_details.email,
    licenseKey,
  });

  // Deliver the key to the customer
  await sendLicenseEmail(session.customer_details.email, licenseKey);
}

4. Deliver the key

Options for delivering the license key to the customer:
  • Email — Send an automated email with the key after payment.
  • Success page — Redirect to a page that displays the key (retrieve it by session ID).
  • Customer portal — Store keys in your database and let users view them in their account.
app.get("/success", async (req, res) => {
  const { session_id } = req.query;
  const license = await db.getLicenseByStripeSession(session_id);

  if (!license) {
    return res.status(404).send("License not found. Please check your email.");
  }

  res.render("success", { licenseKey: license.licenseKey });
});

Subscriptions

For recurring payments, you’ll generate a license when the subscription starts and revoke it when the subscription ends.

Events to handle

Stripe EventAuthForge Action
customer.subscription.createdCreate a license key
customer.subscription.deletedRevoke the license key
customer.subscription.updatedExtend expiry on renewal
invoice.payment_failedOptionally revoke or warn

Complete subscription handler

app.post(
  "/webhooks/stripe",
  express.raw({ type: "application/json" }),
  async (req, res) => {
    const sig = req.headers["stripe-signature"];
    let event;

    try {
      event = stripe.webhooks.constructEvent(
        req.body,
        sig,
        process.env.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      return res.sendStatus(400);
    }

    switch (event.type) {
      case "customer.subscription.created": {
        const subscription = event.data.object;
        await handleSubscriptionCreated(subscription);
        break;
      }
      case "customer.subscription.deleted": {
        const subscription = event.data.object;
        await handleSubscriptionDeleted(subscription);
        break;
      }
      case "customer.subscription.updated": {
        const subscription = event.data.object;
        await handleSubscriptionUpdated(subscription);
        break;
      }
      case "invoice.payment_failed": {
        const invoice = event.data.object;
        await handlePaymentFailed(invoice);
        break;
      }
    }

    res.sendStatus(200);
  }
);

Subscription created — generate license

async function handleSubscriptionCreated(subscription) {
  // Guard against duplicate processing (Stripe retries webhooks)
  const existing = await db.getLicenseBySubscription(subscription.id);
  if (existing) return;

  const response = await fetch("https://api.authforge.cc/v1/licenses", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      appId: process.env.AUTHFORGE_APP_ID,
      quantity: 1,
      expiresAt: new Date(subscription.current_period_end * 1000).toISOString(),
      maxHwidSlots: 1,
      label: `Stripe sub ${subscription.id}`,
    }),
  });

  const data = await response.json();
  const licenseKey = data.licenses[0].licenseKey;

  await db.saveSubscriptionLicense({
    stripeSubscriptionId: subscription.id,
    customerId: subscription.customer,
    licenseKey,
  });

  const customer = await stripe.customers.retrieve(subscription.customer);
  await sendLicenseEmail(customer.email, licenseKey);
}

Subscription deleted — revoke license

async function handleSubscriptionDeleted(subscription) {
  const record = await db.getLicenseBySubscription(subscription.id);
  if (!record) return;

  await fetch(
    `https://api.authforge.cc/v1/licenses/${record.licenseKey}`,
    {
      method: "PUT",
      headers: {
        Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ action: "revoke" }),
    }
  );
}

Subscription updated — extend expiry on renewal

async function handleSubscriptionUpdated(subscription) {
  if (subscription.status !== "active") return;

  const record = await db.getLicenseBySubscription(subscription.id);
  if (!record) return;

  await fetch(
    `https://api.authforge.cc/v1/licenses/${record.licenseKey}`,
    {
      method: "PUT",
      headers: {
        Authorization: `Bearer ${process.env.AUTHFORGE_API_KEY}`,
        "Content-Type": "application/json",
      },
      body: JSON.stringify({
        action: "extend",
        expiresAt: new Date(subscription.current_period_end * 1000).toISOString(),
      }),
    }
  );
}

Payment failed — optional revocation

async function handlePaymentFailed(invoice) {
  const subscriptionId = invoice.subscription;
  if (!subscriptionId) return;

  const record = await db.getLicenseBySubscription(subscriptionId);
  if (!record) return;

  // Option 1: Revoke immediately
  // await revokeLicense(record.licenseKey);

  // Option 2: Send a warning email, revoke after grace period
  const customer = await stripe.customers.retrieve(invoice.customer);
  await sendPaymentFailedEmail(customer.email);
}

Idempotency

Stripe retries webhook deliveries on failure. Guard against duplicate license creation by:
  1. Storing the Stripe session/subscription ID alongside each license in your database.
  2. Checking for an existing record before creating a new license.
  3. Returning early if a license already exists for that payment.

Next steps