
How We Sell a WordPress Theme With a Stripe Payment Link
No store plugin and no licence server: a Stripe Payment Link, a static thank-you page and one small serverless function that checks the payment before it hands over the zip.
When we released Unhurried Pro, the paid version of our free WordPress theme, we needed a way to sell one zip file. The usual answers were heavier than the job: a WooCommerce store just to sell a WooCommerce theme, a digital-downloads plugin, or a marketplace that takes a large cut. We wanted something small, cheap to run and easy to reason about.
Here is the setup we ended up with, and the one mistake it was designed to avoid.
The pieces
- A Stripe Payment Link for the product. Stripe hosts the checkout page, takes the card, calculates sales tax and emails the receipt.
- A static thank-you page on our site. After payment, Stripe redirects there and adds the checkout session ID to the address.
- One serverless function on Netlify. The Download button calls it with that session ID.
- A private GitHub release that holds the zip.
What the function does
The function takes the session ID and asks Stripe about it. It checks two things: that the payment status is paid, and that the session came from our Unhurried Pro payment link, not from some other product. Only then does it fetch the zip from the private GitHub release and stream it back as a download. Anything else gets a plain message asking the buyer to email us with their receipt number.
The Stripe key it uses is a restricted key that can only read checkout sessions. It cannot create charges, issue refunds or see customers. The GitHub token can only read one private repository. Both live as secret environment variables in Netlify, never in the code.
The mistake it avoids
The first version of the plan was simpler: put the zip in the site's public folder at a hard-to-guess address and redirect buyers there. Then we noticed that our site's code lives in a public GitHub repository. Anything committed there is readable by anyone, including a paid zip at a secret path. The file would have been free to anyone who looked.
So the zip never touches the public repository. The public repo holds only the function's code, which is harmless to read, and the file itself stays in a private release.
What it costs
Stripe's normal card fees, plus a small fee for its automatic tax calculation. There is no plugin licence, no store subscription and no server to maintain; the function runs only when someone clicks Download.
When this setup is not enough
If you sell many products, need licence keys, or want automatic update notices inside WordPress, a dedicated digital-sales platform will serve you better. For one product with an occasional new version, a payment link and a single function is plenty. A new version is a new private release and a one-line change to the function.
See Unhurried ProThe theme this setup sells