Software Licensing with Stripe: How the Checkout-to-License Flow Actually Works
Most teams that sell a licensed software product end up gluing two systems together by hand: whatever handles payment, and whatever handles license keys. The usual result is a webhook handler written under deadline pressure, a few silent edge cases, and a support inbox full of "I paid but never got my key" emails.
PermitCore's Store is built so that gap doesn't exist. This post walks through what actually happens, end to end, from the moment a customer clicks "Buy" to the moment their license key lands in their inbox.
It's Your Stripe Account, Not PermitCore's
The Store connects to your own Stripe account — you provide your Stripe secret key in the Admin panel, and every charge settles directly to you. PermitCore never touches the money and takes 0% platform commission on Store sales; you only pay Stripe's own standard processing fees, same as you would selling anything else through Stripe.
That matters for how the rest of this works: PermitCore only finds out a sale happened when Stripe tells it — via a webhook — not by holding the funds itself.
The Flow, Step by Step
- A customer visits your storefront (
/store/{your-slug}), picks a product, and clicks Buy. - PermitCore creates a Stripe Checkout Session and a matching
Orderrow in Pending status, then redirects the customer to Stripe's own hosted checkout page. - The customer pays. Stripe fires a
checkout.session.completedwebhook to PermitCore'sStoreWebhookController, signed with your webhook secret. - The controller verifies the signature, finds the matching Pending order, generates a real license key for it, encrypts it, and marks the order Completed — all in one database transaction.
- A confirmation email with the license key (or an account-link, if the customer registered a Store account) is queued and sent within the minute.
The customer never has to refresh a page or wait for polling — Stripe's webhook is the trigger, and the whole thing typically completes in under a second after payment confirms.
The Store admin panel — connect your Stripe secret key once, and every product listed here becomes purchasable through a hosted checkout page.
A Simplified Look at the Webhook Handler
The real handler deals with cart checkouts, invoices, and email retries — more than makes sense to reproduce here. The core shape looks like this:
// StoreWebhookController.cs (simplified) switch (stripeEvent.Type) { case "checkout.session.completed": await HandleCheckoutCompleted(stripeEvent); break; case "charge.refunded": await HandleRefund(stripeEvent); break; // ...payment_intent.payment_failed, checkout.session.expired, etc. }
Inside HandleCheckoutCompleted, the order lookup, license generation, and status update all happen inside one SaveChangesAsync() — so a crash mid-request can't leave a customer with a "completed" order and no actual license, or a license with no record of the order that paid for it.
What About Subscriptions?
The Store already supports recurring Stripe prices for checkout, and PermitCore's separate metered billing system can charge per usage event through the same Stripe connection. Using a recurring price to automatically extend an existing license's expiry date on each renewal — true license leasing — is on our roadmap but not shipped yet; today, a subscription product issues a license the same way a one-time purchase does, at first checkout.
What This Means for You
You keep full ownership of your payment relationship — your Stripe account, your payout schedule, your dispute handling. PermitCore's job is just to turn "this Stripe event happened" into "this customer now has a working, encrypted license key" reliably, without you writing that glue code yourself.
If you want the admin-side setup steps rather than the architecture, see Setting Up a License Store.