- Eric Vegas
- 2026-04-01
- 2026-09-17
The problem
Selling several digital products across different platforms leaves the offers scattered and the delivery manual. Every product needs its own checkout, its own way of confirming the payment happened, and its own way of granting the buyer access to the thing they bought.
One place that holds every offer, and one backend that handles checkout, payment confirmation and access for all of them.
What I built
A Next.js application with Supabase for authentication and data, checkout through Stripe, and webhook handlers for three separate payment providers. Purchases grant entitlements, and entitlement routes deliver the product or provision access to it. It also serves as the backend for other properties: the booking form on ericvegas.com posts to it, and so does the voice agent.
How it works
Checkout creates a session with the relevant provider. The provider's webhook fires, the handler verifies it, and the purchase is recorded. Access routes then grant whatever the product is: a download, a repository invitation, or an unlock on a product page. Writes that need elevated database permissions happen in server routes, never in the browser.
My role
Eric built the application: the schema, the auth, every server route, all three webhook handlers and the entitlement model. Stripe, Gumroad and Lemon Squeezy take the money; Supabase stores the data; Vercel runs it. The work is the layer that makes three different providers produce one consistent answer to "has this person paid for this thing".
The hard part
Three payment providers is three different truths about the same purchase, arriving at different times and in different shapes. A webhook is also an unauthenticated public URL that anyone can POST to, so "a request said this was paid" cannot be allowed to mean "this was paid". The Lemon Squeezy handler computes an HMAC-SHA256 of the raw body and compares it in constant time before it does anything, and every elevated write happens in a server route where the service key cannot reach the browser.
Proof
Go and open it
Artifacts
- linkpayhub.com
- More than twenty server routes covering checkout, three payment-provider webhooks, OAuth, access provisioning, booking and notifications.
- This site's own booking form, which posts to a LinkPayHub endpoint
Claims, and what backs each one
LinkPayHub is a live Next.js application using Supabase for auth and data and Stripe for checkout.
The site returns HTTP 200, and its dependency manifest and route sources show @supabase/ssr, @supabase/supabase-js and @stripe/stripe-js in use across authentication, dashboard and checkout routes.
It handles webhooks from three separate payment providers, one of which is verified with an HMAC signature check.
Separate webhook routes exist for Stripe, Gumroad and Lemon Squeezy. The Lemon Squeezy handler computes an HMAC-SHA256 digest of the raw body and compares it in constant time before acting.
ericvegas.com depends on it. The booking form on this site posts to a LinkPayHub endpoint, which writes to a database and pushes a notification.
The /api/book route in this site's own source forwards the submission to linkpayhub.com/api/studio-booking. That endpoint writes to Supabase and sends a push notification.
Results from client deployments are shared with permission on a call.
What I learned
Entitlement is the thing worth modelling, not checkout. Once "this person has access to that product" is one record with one meaning, adding a fourth provider is a handler, not a redesign. This is the backend the booking form and the voice agent both ended up using.
Technologies
- Next.js
- TypeScript
- Supabase
- Stripe
- Gumroad
- Lemon Squeezy
- Google OAuth
- Vercel
Related work
If something here is close to a problem you have, the useful first message is what the thing has to do and what it has to talk to.
Work with me →