
JasonTL;DR: I needed to sell access to a private GitHub repo. Zip downloads don't get updates, license...
TL;DR: I needed to sell access to a private GitHub repo. Zip downloads don't get updates, license keys are overkill, and multiple payment providers rejected my business. I ended up building a fully automated system that handles the whole flow: payment webhook fires, signature verified, GitHub username captured via OAuth, collaborator invite sent in seconds. It works great, but turns out Polar.sh already has this built in and does it better. I didn't end up using my version, but I learned a ton building it.
I built a boilerplate I wanted to sell. The product was done. The hard part was figuring out how to actually deliver it.
You build a starter kit, a boilerplate, a template, something developers would pay for. Now what? How do you actually get it to them after they pay?
I spent way too long researching this. The options were all bad in different ways:
What I actually wanted was dead simple: customer pays, customer gets access to the private repo, automatically, instantly.
Before I even got to the technical stuff, I hit a wall with payment providers.
My registered business is a design and consulting firm. I tried signing up with Lemon Squeezy and a couple other platforms to sell digital products. They rejected me. I explained I was selling one-time digital products, boilerplate code, starter kits, not consulting services. Didn't matter. Still rejected. Some of them were pretty rude about it too.
At this point I wasn't sure what payment provider I'd even use. I just knew I needed something that could fire a webhook on payment so I could automate the rest myself.
After a lot of searching I came across a project doing something similar, using payment webhooks to trigger GitHub collaborator invitations. Private repo, read-only access. Customer can clone, pull updates, see full git history. That was exactly what I wanted. They were charging $30 for it. I figured I could probably make it myself, and I needed to solve the problem anyway, so two birds.
I looked at how hard it would be to build my own version. Turns out the pieces are all there:
pull (read-only) so customers can clone but can't pushThe idea was to build a standalone system I could run on my own server. Any payment provider with webhooks would work. I'd handle the GitHub OAuth, the invitation, the email, everything myself.
So I built it.
User clicks "Buy"
|
GitHub OAuth (capture username)
|
Payment checkout
|
Webhook fires -> verify signature -> invite to repo -> send email
|
Customer has access in seconds
A Next.js app with five pieces:
The whole thing is designed to be payment provider agnostic. Plug in any provider that sends webhooks and it works.
Your payment provider doesn't know the buyer's GitHub username. You need to capture it before they hit checkout.
The flow: user clicks "Buy" -> GitHub OAuth popup -> authorize -> redirect to checkout with the username passed along.
// After OAuth callback, redirect to checkout with GitHub info
const checkoutUrl = new URL(CHECKOUT_URL);
checkoutUrl.searchParams.set('gh_username', githubUser.login);
checkoutUrl.searchParams.set('gh_user_id', String(githubUser.id));
The payment provider includes these fields in the webhook payload, so when the payment completes, I know exactly who to invite.
Anyone can POST to your webhook endpoint. Without signature verification, an attacker could grant themselves free access.
import { createHmac, timingSafeEqual } from 'crypto';
function verifySignature(payload: string, signature: string): boolean {
const expected = createHmac('sha256', WEBHOOK_SECRET)
.update(payload)
.digest('hex');
return timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Two things that bit me:
timingSafeEqual, not ===. Regular string comparison leaks timing information that can be exploited.A customer buys your boilerplate, then buys an upgrade or a second product. Without UPSERT, the second webhook fails with a unique constraint violation on their email:
INSERT INTO customers (email, name, amount_paid, ...)
VALUES (...)
ON CONFLICT (email) DO UPDATE SET
amount_paid = EXCLUDED.amount_paid,
order_id = EXCLUDED.order_id,
updated_at = NOW()
If a customer doesn't accept their invitation, it stays pending. After 50 pending invitations, GitHub blocks new ones entirely. I track invitation status and built an admin panel to monitor this.
The GitHub API goes down. Rate limits hit. Network blips happen. A customer paying for your boilerplate can't just get an error and be told to contact support.
I built a retry system with exponential backoff:
Attempt 1: 1 second
Attempt 2: 2 seconds
Attempt 3: 4 seconds
Attempt 4: 8 seconds
...
Attempt 10: 1 hour
Errors get classified automatically:
After 10 failed attempts, the item moves to a dead letter queue and I get notified.
The GitHub token exchange endpoint is https://github.com, NOT https://api.github.com:
# WRONG, returns HTML login page
POST https://api.github.com/login/oauth/access_token
# CORRECT, returns the token
POST https://github.com/login/oauth/access_token
Every OAuth guide I found was correct, but my muscle memory from using the GitHub API kept making me use the API subdomain.
| Component | Technology |
|---|---|
| Framework | Next.js 16 (App Router) |
| Language | TypeScript (strict mode) |
| Database | PostgreSQL on Neon |
| GitHub API | Octokit |
| Resend | |
| Validation | Zod |
| Testing | Vitest, all passing |
Built to run on any hosting provider. Runs on Vercel's free tier for low volume.
After trying all the alternatives, this approach has real advantages:
git pull. No re-downloading zips.pull permission means they can clone but can't push to your repo.The main downside is the 50 pending invitation limit and the fact that it's tied to GitHub specifically. But for selling developer tools to developers, that's not really a limitation.
While I was building all of this, I ended up going with Polar.sh for payments because they actually accepted my business (unlike Lemon Squeezy). And when I dug into their platform, I realized they already handle everything I was building. GitHub username capture, repository invitations, access management, all of it. Built right into their platform.
So the standalone tool I spent all this time building was redundant. Polar handles the exact flow I was trying to automate: customer pays, Polar captures their GitHub username, Polar sends the repo invitation, customer gets access. I just had to get approved and flip it on.
My tool works, all tests pass, the whole pipeline runs, but I never needed to deploy it. I learned a ton building it though. Webhook signature verification, OAuth flows, retry systems with exponential backoff, how GitHub's collaborator API actually works. That's the kind of stuff you don't pick up from tutorials.
But now I'm genuinely curious. I built this because someone was charging $30 for something similar and I figured I could do it myself. Is this something anyone would actually find useful? If you're selling a boilerplate or a starter kit and you don't want to be locked into Polar's built in solution, would a standalone tool like this be worth it to you? Would you want it open sourced?
Let me know in the comments. I'm trying to figure out if this is worth shipping (open source it / buy me a coffee) or if it just stays as a learning project. Almost feels pointless to sell? Either way was fun to build.
Built with Next.js, TypeScript, and a lot of time reading webhook docs.