How I Built a System to Automatically Sell Access to a Private GitHub Repo

Published: (February 7, 2026 at 12:43 PM EST)
9 min read
Source: Dev.to

Source: Dev.to

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:

  1. Payment webhook fires → signature verified
  2. GitHub username captured via OAuth
  3. Collaborator invite sent in seconds

It works great, but it 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.

The Problem

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?

Options I evaluated

OptionProsCons
Zip file downloadSimple, works out‑of‑the‑boxNo updates, no git history, no pull/diff
License‑key gatingCan lock accessRequires custom CLI + infra – overkill
npm private packageFamiliar for devsRequires registry auth → friction kills conversions
GitHub Sponsors (private repos)Built‑in GitHub accessSubscription‑only, not good for one‑time purchases
Manual delivery via payment platformWorks for a few salesBecomes impossible at scale (e.g., 3 sales at 2 am)

What I actually wanted was dead simple: customer pays → customer gets access to the private repo automatically, instantly.

Payment‑Provider Roadblock

My registered business is a design and consulting firm. I tried signing up with Lemon Squeezy and a few other platforms to sell digital products, but they rejected me.

  • I explained I was selling one‑time digital products (boilerplate code, starter kits), not consulting services.
  • The rejection remained, and some providers were outright rude.

At that point I only knew I needed a provider that could fire a webhook on payment so I could automate the rest myself.

Inspiration

I found a project that did something similar:

  • Payment webhook → GitHub collaborator invitation
  • Private repo, read‑only access (clone + pull updates, full git history)

They charged $30 for it. I thought I could build it myself – two birds, one stone.

Why It Was Feasible

  • GitHub API lets you add collaborators programmatically.
  • Any payment provider that fires webhooks can trigger the flow.
  • Permissions can be set to pull (read‑only) so customers can clone but can’t push.

My Solution Architecture

A stand‑alone Next.js app that is payment‑provider agnostic.

User clicks "Buy"
       |
GitHub OAuth (capture username)
       |
Payment checkout
       |
Webhook fires → verify signature → invite to repo → send email
       |
Customer has access in seconds

Main components

ComponentDescription
GitHub OAuthCaptures the buyer’s GitHub username before checkout
Webhook handlerReceives payment confirmation, verifies HMAC‑SHA256 signature
GitHub API clientSends the repository invitation automatically
PostgreSQLTracks every customer (33 fields of data)
ResendSends the welcome email

Detailed Flow

1. Capture GitHub username before checkout

The payment provider doesn’t know the buyer’s GitHub username, so we must capture it first.

// 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, allowing us to know exactly who to invite when the payment completes.

2. Verify webhook signature

Anyone can POST to your webhook endpoint. Without 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));
}

Gotchas

  • Use the raw request body, not the parsed JSON – re‑serialization changes whitespace and breaks the signature.
  • Use timingSafeEqual instead of === – plain string comparison leaks timing information.

3. Upsert customer record

A customer may buy an upgrade or a second product. Without an 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();

4. Handle pending invitations

If a customer doesn’t accept their invitation, it stays pending. After 50 pending invitations GitHub blocks new ones entirely.

  • I track invitation status.
  • I built an admin panel to monitor pending invites and manually resend or revoke them.

5. Retry on GitHub API failures

The GitHub API can go down, hit rate limits, or suffer network blips. A customer paying for your boilerplate shouldn’t just see an error and be told to contact support.

Exponential back‑off strategy

AttemptDelay
11 second
22 seconds
34 seconds
48 seconds
101 hour

Error classification

  • Retryable – network timeouts, rate‑limit responses, 5xx errors → go through the back‑off loop.
  • Non‑retryable – 4xx client errors (e.g., invalid payload) → log and alert.

Lessons Learned

  1. Payment providers can be finicky about the type of digital goods you sell.
  2. Signature verification must use the raw payload and a timing‑safe compare.
  3. UPSERT (or equivalent) prevents duplicate‑key errors when a buyer makes multiple purchases.
  4. Pending GitHub invites can silently block you – monitor and clean them up.
  5. Robust retry logic is essential when you depend on third‑party APIs.

Bottom Line

I built a fully automated, provider‑agnostic system that:

  • Captures a buyer’s GitHub username via OAuth.
  • Listens for a payment webhook, verifies it securely.
  • Invites the buyer as a read‑only collaborator to a private repo.
  • Sends a welcome email with the repo link.

It works, but Polar.sh already offers this functionality out‑of‑the‑box and does it better, so I ultimately switched to their service. Still, the experience gave me a deep dive into OAuth, webhook security, GitHub’s API, and reliable automation patterns.

Queue with Back‑off

  • Permanent failures (e.g., user not found, repo not found, 401/403) → send to a dead‑letter queue for manual review.
  • After 10 failed attempts the item moves to the dead‑letter queue and I get notified.

GitHub Token Exchange Endpoint

The correct endpoint is https://github.com, not https://api.github.com.

# WRONG – returns an HTML login page
POST https://api.github.com/login/oauth/access_token
# CORRECT – returns the token
POST https://github.com/login/oauth/access_token

My muscle memory from using the GitHub API kept me pointing at the API sub‑domain, but the OAuth flow lives on the main site.

Stack Overview

ComponentTechnology
FrameworkNext.js 16 (App Router)
LanguageTypeScript (strict mode)
DatabasePostgreSQL on Neon
GitHub APIOctokit
EmailResend
ValidationZod
TestingVitest (all passing)

Built to run on any hosting provider. Currently runs on Vercel’s free tier for low volume.

Why This Approach Has Real Advantages

  • Instant updates – Push a fix to main; every customer can git pull – no need to re‑download zip files.
  • Full git history – Customers can see why things were built the way they were.
  • Read‑only by defaultpull permission lets them clone but not push to the repo.
  • No extra tooling – Only git is required; no license keys, custom CLIs, or registry auth.
  • Revocable access – Need to charge back? Remove the collaborator with a single API call.

Drawbacks

  • 50 pending invitation limit – GitHub caps the number of outstanding invites.
  • GitHub‑centric – Tied to GitHub; not a universal solution.

For selling developer tools to developers, the drawbacks are usually acceptable.

What I Ended Up Using Instead

While building the tool I switched to Polar.sh for payments because they accepted my business (unlike Lemon Squeezy). Their platform already handles everything I was recreating:

  • GitHub username capture
  • Repository invitations
  • Access management

In short, Polar automates the exact flow I was trying to build:

  1. Customer pays.
  2. Polar captures their GitHub username.
  3. Polar sends the repo invitation.
  4. Customer gets access.

All I needed was approval, then I could flip the feature on.

Reflections

My tool works: all tests pass, the pipeline runs, but I never needed to deploy it. Still, I learned a ton:

  • Webhook signature verification
  • OAuth flows
  • Retry systems with exponential back‑off
  • How GitHub’s collaborator API actually works

These are the kinds of details you don’t get from tutorials.

Open Question

I built this because I saw someone charging $30 for a similar solution and thought I could do it myself.

  • Would anyone actually find this useful?
  • If you’re selling a boilerplate or starter kit and don’t want to be locked into Polar’s built‑in solution, would a standalone tool like this be worth it?
  • Would you want it open‑sourced?

Let me know in the comments. I’m trying to decide whether to ship it (open source it / “buy me a coffee”) or keep it as a learning project. It feels a bit pointless to sell, but the build was fun.

TL;DR

Built with Next.js, TypeScript, and a lot of time reading webhook docs. 🚀

0 views
Back to Blog

Related posts

Read more »

GitHub down 😭

Post Is this the first time for github?? Comments Ben Halpern – Feb 9 Not even the first time in the past week that some part of GitHub is down… but this is a...