I'm a 19-year-old solo developer who just shipped my first SaaS in 5 days. It's live at https://bountydesk.vercel.app and open source on https://github.com/kushagra1607/bountydesk
This is a no-BS technical breakdown of the stack, every architectural decision, what worked, and what I'd do differently.
The product (1-minute context)
BountyDesk is a submission tracker + Markdown report builder for bug bounty hunters. Free tier; $7/mo Pro. The stack is what this post is about.
The full stack
- Frontend: Next.js 16 (App Router, Server Actions, Node.js runtime)
- Styling: Tailwind v4
- Auth + DB: Supabase (Postgres + RLS + Auth)
- Billing: Paddle (Merchant of Record - handles tax in 100+ countries)
- Analytics: PostHog (self-host or cloud, both work)
- Hosting: Vercel
Monthly cost to run: ~$0. All free tiers.
Architecture decisions
1. Supabase RLS for everything
Every table (submissions, programs, profiles) has Row-Level Security enabled. The policy shape:
create policy "own rows" on public.submissions
for all using (auth.uid() = user_id)
with check (auth.uid() = user_id);
A user cannot read or write another user's data, ever, at the database level. Not just app-layer enforcement - Postgres itself blocks it.
Gotcha I hit: my initial profiles table allowed user UPDATE without column restriction. Any user could run UPDATE profiles SET plan='pro' WHERE id=auth.uid() from their browser console - instant free Pro. Caught it in pre-launch audit. Fixed by revoking UPDATE entirely from the authenticated role; only the Paddle webhook (service_role) can write.
2. Paddle as Merchant of Record
I'm in India. Selling globally means VAT in 27 EU countries, GST in Australia, etc. Paddle is a Merchant of Record - they take legal/tax responsibility. I just receive a payout in INR.
Fee: 5% + $0.50 per transaction. On a $7/mo subscription, I see ~$6.15 in my Paddle balance, ~$6.00 after Payoneer FX → INR.
3. Webhook signature verification
The webhook handler verifies the Paddle HMAC signature on the raw body (before any parsing):
const signature = request.headers.get("paddle-signature") ?? "";
const body = await request.text();
try {
const event = await getPaddle().webhooks.unmarshal(
body, secret, signature
);
} catch {
return NextResponse.json({ error: "bad signature" }, { status: 400 });
}
If you parse the body first (request.json()), Node mutates whitespace and signature check fails. Always read raw text first.
4. PostHog → Slack for real-time alerts
PostHog has CDP (Customer Data Platform) functions that fire actions on event matches. I have three:
-
user_signed_up→ Slack: "New signup!" -
upgrade_checkout_opened→ Slack: "Upgrade clicked!" -
subscription_activated(fired server-side from the Paddle webhook) → Slack: "PAYMENT RECEIVED!"
I literally know the moment a customer pays before Paddle even shows it in their dashboard.
5. AGPL-3.0 license
Open source with a viral copyleft. If someone forks the code, modifies it, and runs it as a competing SaaS, they must open-source their changes too. Same license PostHog, Plausible, and Cal.com use.
What I'd do differently
- Start the HN account age clock 30 days BEFORE launch. HN auto-flags new-account Show HN posts. Learned this the hard way.
- Build distribution while you build the product. I shipped the code in 5 days but spent 0 days on Twitter or community. By launch day I had no audience to broadcast to.
-
Get a real domain. vercel.app subdomain looks indie-ish. ~$10/year for
bountydesk.comwould have signaled "real product." - Open source from day 1, not day 30. Stars and contributions compound.
What I'd repeat
- Paddle. Selling globally on day 1 with no compliance burden is magical.
- Supabase RLS. Catches what app-layer code forgets.
- PostHog + Slack alerts. Real-time visibility into every conversion.
- Vercel free tier. Zero deploy friction.
The code
All ~70 source files on GitHub: https://github.com/kushagra1607/bountydesk
Interesting files:
-
src/app/api/paddle/webhook/route.ts- webhook + signature verification -
src/lib/supabase/admin.ts- server-only service-role client -
supabase/schema.sql- full schema with RLS policies -
src/components/ReportBuilder.tsx- the report generator
Try it
Live: https://bountydesk.vercel.app
Sign up, log a fake submission, tell me what sucks. I respond to all DMs/comments.
— Kushagra (@bountydesk on X)
Top comments (0)