Skip to content
Aayush Thakur
All work

Case study · Founder & CTO

Zen-Z

Small groups of strangers, one plan, and nobody has to be the organiser.

Role
Founder & CTO, sole engineer
Timeline
Jul 2026 – now
Platforms
Android, iOS (PWA) and web
Stack
React Native, Next.js, Supabase, PayU
Zen-Z admin dashboard: bookings this week by activity, groups formed, unmatched students, pending reports and payments to check, the students, reports, referrals and analytics tools, and the matching queue
Zen-Z booking summary: Cafés, Sunday 11th at 5:00 pm, group of 4–5, under ₹200, Save my seat
Zen-Z payment screen: 8-Ball Pool, Saturday 10th at 6:00 pm, group of 4, ₹70, Pay ₹70
Problem
Meeting new people at college depends on luck, or on someone organising.
Built
The student app, the admin dashboard and the Supabase backend with PayU payments. Solo.
Result
Live on Google Play: 150+ users in its first 2 days, ₹33K+ revenue.

01 — Problem

Meeting people shouldn't depend on luck.

Or on someone volunteering to organise.

Zen-Z matches college students into small groups of strangers (4–5 people; sports up to 14) and gives each group a plan: a café, a dinner, a movie or a game. Cafés and dinners are free to book (venues pay a commission); movies and sports are paid. Demand came first: 65+ students joined the waitlist before launch.

02 — What I built

App, admin, backend. One engineer.

Product decisions, data model, app, internal tools, payments, CI and releases.

  1. Zen-Z booking, pick a game: Box Cricket, Football, 8-Ball Pool and Pickleball with prices

    01

    Pick an activity

    Box cricket, football, pool or pickleball.

  2. Zen-Z booking, pick a slot: a fixed weekly slot, Sunday 11th at 5:00 pm, selected

    02

    Pick a slot

    One weekly time, so groups form.

  3. Zen-Z booking, budget range: under ₹200 selected, with ₹200–400 and ₹400+ options

    03

    Set a budget

    So groups spend alike.

  4. Zen-Z booking, group dynamic: Surprise me (mixed) selected, with Women only and Men only

    04

    Choose the group

    Mixed, women only or men only.

  5. Zen-Z booking summary: Cafés, Sunday 11th at 5:00 pm, group of 4–5, under ₹200, Save my seat

    05

    Confirm the plan

    Review every choice before paying.

  6. Zen-Z payment screen: 8-Ball Pool, Saturday 10th at 6:00 pm, group of 4, ₹70, Pay ₹70

    Pay

    Pay to hold the seat

    PayU checkout; only a verified webhook confirms it.

AReact Native (Expo)

The student app

Android, iOS (PWA) and web from one codebase, 20 screens, realtime group chat, push notifications.

BNext.js

The admin dashboard

A drag-and-drop board the team uses to hand-match every group, plus moderation and analytics.

CSupabase · Deno

The backend

Postgres with RLS on all 28 tables, 7 Edge Functions, 3 cron jobs, PayU payments.

03 — The website

zen-z.site: a Z you can pull.

I redesigned the marketing site from scratch: cream paper, Zen-Z red, stickers and doodles.

zen-z.site on desktop: the headline 'Spend a café evening with 4 people you haven't met', a Get it on Google Play button, a phone and stickers, and the glossy red Z hanging on a cord
zen-z.site on a phone: the same headline with a rolling plan, the hanging Z and a Get Zen-Z on iPhone button
zen-z.site on a phone: step 4 of how it works, 'Chat opens 48h before', with a hand-drawn illustration
  1. 1

    A Z you can pull

    The red Z hangs on a cord you can drag, with rope physics I wrote by hand (~150 lines, no 3D library). The server renders a still Z first, so nothing needs JavaScript.

  2. 2

    Payments never broke

    27 contract checks guard the PayU payment-return URL, the iPhone install flow and every legal page. They pass against production.

  3. 3

    98 on Lighthouse

    Desktop performance on the live site (median of 3 runs), and 100 for accessibility, best practices and SEO.

Visit zen-z.site (opens in new tab)

04 — Architecture

Boring, proven parts. The database enforces the rules.

Access rules live in the database (row-level security on all 28 tables), so they hold even if the app has a bug. Money only moves in Edge Functions, and a trigger seals free bookings so the client can’t fake a ₹0 price.

App

React Native + Expo

20 screens · Android, iOS (PWA) and web from one codebase

Admin dashboard

Next.js

Drag-and-drop board to hand-match every group, moderation, analytics

Backend

Supabase

  • Postgres28 tables, RLS on every one
  • Edge Functions7, Deno
  • Realtimegroup chat
  • Authemail OTP
  • pg_cron3 jobs

Payments

PayU

Orders created on the server. SHA-512 signed webhooks, verified in constant time, confirm the seat.

Zen-Z, end to end. Everything except the third-party services was designed and built by one person. Tested in GitHub Actions; ships to Google Play (EAS) and Vercel.

05 — Incident

A paid booking never confirmed. I fixed it the same day.

16 Sep 2026Fixed the same day

What shipped that day

  • Every webhook call is logged with its outcome in payment_webhook_events.
  • An admin “Needs your attention” queue: unpaid for 10+ minutes, or paid but the slot has passed.
  • An admin-only admin_mark_booking_paid() RPC, covered by a pgTAP test, a WhatsApp support link on the payment screen and a “Payments to check” count on the dashboard.

The guarantee now

No booking is confirmed without a verified payment, every webhook call is on record, and anything stuck reaches the admin queue.

What happened

  1. Paid

    A student's real UPI payment succeeds.

  2. No webhook

    PayU never calls back, and webhook calls aren't logged yet.

  3. Traced

    Same day: confirmed the payment in PayU; no webhook had been logged.

  4. Fixed · 14 files

    Shipped the fix the same afternoon.

The verification was already in place: PayU’s docs give the wrong webhook hash for Payment Links, so on 29 Aug I worked out the real one from a live ₹1 transaction, and only a signed, server-verified webhook could confirm a booking. On 16 Sep, a student’s UPI payment went through but PayU’s webhook never landed, and webhook calls weren’t logged yet, so there was no trail and the booking still showed unpaid.

A week later, checkout moved to its own tab and the booking re-checks itself when the student returns (Payment Links never redirect back). Cancel-then-pay on a stale tab is logged for a manual refund, not confirmed.

payu-webhook/index.tssimplified sketch
Deno.serve(async (req) => {
  const p = Object.fromEntries(await req.formData());

  // PayU's docs show a 5-udf formula. Payment Links really use all ten,
  // confirmed byte for byte against a live ₹1 transaction.
  const expected = await sha512([
    SALT, p.status,
    p.udf10, p.udf9, p.udf8, p.udf7, p.udf6,
    p.udf5, p.udf4, p.udf3, p.udf2, p.udf1,
    p.email, p.firstname, p.productinfo, p.amount, p.txnid, KEY,
  ].join("|"));

  // Constant-time compare: a bad signature confirms nothing.
  if (!timingSafeEqual(expected, p.hash)) return reject(p, "invalid_signature");

  // Cancelled, then paid on a stale tab? Logged, not confirmed.
  const outcome = await settleBooking(p);

  await logEvent(p, outcome);
  return ok();
});

// Best-effort: logging never changes the response.
async function logEvent(p, outcome) {
  try {
    await db.from("payment_webhook_events").insert({ txnid: p.txnid, outcome });
  } catch {}
}

06 — Results

Paying students, live on Play.

Live on Google Play, and pitched to the judging panel at JNU's Shark Tank (Sep 2026).

users in the first 2 days after launch
150+
total revenue so far
₹33K+
students on the waitlist before launch
65+
views on Instagram · 550+ followers
140K+

Under the hood

Tables, row-level security on every one
28
SQL migrations
102
Postgres functions
41
Edge Functions
7
pg_cron jobs
3
CI jobs
11
Test suites in CI
37
pgTAP assertions
115
Commits
190

Along the way

  • Moved payments from Razorpay to PayU mid-build.
  • Got past a Play Store rejection with a reviewer sign-in path.
  • Writing the pgTAP tests found and fixed 3 RLS and authorization bugs.