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.
- 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};
- 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",
});
}
}
return NextResponse.json({ success: true }, { status: 200 });
}
- 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)