An event ticket you can't counterfeit, resell in the dark, or use twice.
sTickets is an NFT-based event ticketing platform for the Georgian market. I built it end to end — frontend, API, database and the on-chain ticket layer — then deployed it and kept it running. It went live with around ten organizers and sold thousands of tickets.
The problem
A paper or PDF ticket is a promise that nobody can check. It can be screenshotted, forwarded and sold twice, and the buyer only finds out at the door. The resale market that grows around that is invisible to the people who actually put the event on: once a ticket is sold, the organizer has no view of where it goes and no share of what it goes for.
So the platform had to answer two separate questions at once. Is this ticket real? — which is about provenance. And who profits when it changes hands? — which is about the secondary market. Solving only the first gets you a verification tool. Solving both gets you something an organizer has a reason to switch to.
The life of one ticket
- 01
Browse
A public marketplace of events across nine categories, in English and Georgian, with live availability.
- 02
Purchase
Payment clears through Fastoo, and the ticket is minted on completion — not before. The purchase and the mint have to agree.
- 03
Own
The ticket sits in the buyer's wallet with its full ownership history. Not a PDF, not a screenshot.
- 04
Resell
Listed back to the marketplace at a capped price. The transfer is verified, the seller is paid out, and the organizer takes a 5% royalty on the resale.
- 05
Admit
On event day a one-time QR appears in the holder's profile. It validates once at the door and cannot be duplicated or replayed.
Two sources of truth
The chain is authoritative about ownership. It is also a poor place to run a storefront: you cannot filter a category, sort by date or show live availability against it at the speed a marketplace needs. So the product reads from PostgreSQL and settles on-chain — and those two records are never allowed to disagree.
That is the constraint the whole build turns on, and it shows up everywhere. A payment that clears but a mint that fails, a resale that transfers on-chain while the database still shows the old holder, a QR generated against a ticket that has since moved — each of those is the same bug wearing a different hat. Keeping the on-chain ticket layer in sync with Postgres was the part of this project I spent the most care on.
What I built
Organizer dashboard
Publishing for both paid and free events, behind an approval workflow — an organizer submits, and an event only reaches the public marketplace once it has been reviewed.
Customer marketplace
Browse and buy across nine event categories, bilingual in English and Georgian, with live availability and prices in GEL.
Resale flow
A holder lists a ticket back to the marketplace; the transfer is verified, the seller is paid out, and the organizer earns a 5% royalty on the resale.
Payments
Live payments through Fastoo, wired so that the money clearing and the ticket being minted stay tied to each other.
The API went down, and the API was fine
The frontend ran on Vercel and the API on Render. Requests started failing, and the obvious read — the backend is down — was the wrong one.
Before touching the API I checked whether the request was even leaving the frontend. That is the cheap question, and it splits the problem in half: if nothing is going out, no amount of backend debugging helps. Requests were going out — a great many of them. Then I looked at what Render was actually returning, rather than what the frontend reported.
A loop in the frontend was firing enough calls to trip the rate limiter, and the limiter was doing exactly its job: blocking the endpoint. The service was healthy the whole time. It was being attacked by its own client.
I unblocked the endpoint to restore service, then fixed the request pattern that caused it. Leaving the limiter switched off would have been the faster fix and the wrong one — it was the only thing that noticed the bug.
Stack, and how it shipped
- Frontend
- Next.js
- API
- Express
- Database
- PostgreSQL
- Media
- AWS S3
- Payments
- Fastoo
- Ticket layer
- On-chain NFT
- Repo & CI
- GitLab
- Hosting
- Vercel + Render
Next.js and Express rather than one framework, because the ticket layer needed a long-lived API surface of its own rather than route handlers attached to the storefront. Everything shipped from GitLab with separate dev and prod environments, the frontend deploying to Vercel and the API to Render on merge. I was the only person on it, so the deployment story had to be boring.
Outcome
Live at stickets.ge with around ten organizers and thousands of tickets sold, running in English and Georgian across nine event categories. The parts I am most pleased with are not the visible ones: a ticket that can only be admitted once, and a resale that the organizer earns from instead of losing to.
← Back to the rest of my work