DEV Community

velodeck
velodeck

Posted on

How to Build Automated GitHub Repo Delivery with Next.js 16, Lemon Squeezy, & Octokit

Selling a developer starter kit, boilerplate, or private repository manually is a massive time sink. Every time a purchase comes through, you have to log into GitHub and manually invite the customer's handle.

To solve this, you can build a zero-touch fulfillment pipeline:

Collect the buyer's GitHub username at checkout.

Receive a signed HMAC webhook from Lemon Squeezy.

Verify the payload using the Next.js 16 App Router.

Automate the repository invitation via Octokit.

Here is the complete step-by-step implementation.

  1. Capture the GitHub Handle at Checkout When setting up your product on Lemon Squeezy, pass the buyer's GitHub username in the checkout URL as custom data:

TypeScript
const checkoutUrl = https://your-store.lemonsqueezy.com/checkout/buy/variant_id?checkout[custom][github_username]=${username};

  1. Verify the Signed Webhook Signature To ensure incoming POST requests originate from Lemon Squeezy rather than a malicious source, verify the X-Signature header using your webhook signing secret.

Create app/api/webhooks/lemon-squeezy/route.ts:

TypeScript
import crypto from "node:crypto";
import { NextRequest, NextResponse } from "next/server";
import { Octokit } from "@octokit/rest";

const octokit = new Octokit({ auth: process.env.GITHUB_PAT });

export async function POST(request: NextRequest) {
const secret = process.env.LEMON_SQUEEZY_WEBHOOK_SECRET;
if (!secret) return NextResponse.json("Missing secret", { status: 500 });

const rawBody = await request.text();
const signature = Buffer.from(request.headers.get("X-Signature") ?? "", "hex");

if (!signature.length || !rawBody.length) {
return NextResponse.json("Invalid request", { status: 400 });
}

// HMAC SHA-256 verification
const hmac = Buffer.from(
crypto.createHmac("sha256", secret).update(rawBody).digest("hex"),
"hex"
);

if (!crypto.timingSafeEqual(hmac, signature)) {
return NextResponse.json("Unauthorized signature", { status: 401 });
}

const payload = JSON.parse(rawBody);
const eventName = payload.meta.event_name;

if (eventName === "order_created") {
const githubUsername = payload.meta.custom_data?.github_username;

if (githubUsername) {
  await octokit.rest.repos.addCollaborator({
    owner: process.env.GITHUB_ORG_OR_USER!,
    repo: process.env.GITHUB_PRIVATE_REPO!,
    username: githubUsername,
    permission: "push",
  });
}
Enter fullscreen mode Exit fullscreen mode

}

return NextResponse.json({ success: true }, { status: 200 });
}

  1. Environment Setup Add your keys to your .env.local file:

Code snippet
LEMON_SQUEEZY_WEBHOOK_SECRET=your_webhook_secret
GITHUB_PAT=ghp_your_personal_access_token
GITHUB_ORG_OR_USER=your_github_handle
GITHUB_PRIVATE_REPO=your_private_repo_name
Conclusion
This setup turns your digital product into a completely hands-off system.

Want to skip setting this up manually?

If you want a full production-ready Next.js 16 setup with Supabase authentication, database schemas, UI components, and built-in repository fulfillment, check out Velodeck .

Top comments (0)