Eventos
A screenshot of a ticket is a ticket, and the gate has no way to know.
Ticketing where the hard part is not selling — it is the twenty seconds at the gate, in the dark, with a queue behind you and a phone that will not scan.
Organiser console /organiser/login · [email protected]
Gate scanner /gate/ · [email protected]
Password for both: Eventos!dev1 · payments are deliberately not connected on this demo.
- Ticketing & gate operations
- System design · Engineering · Deployment
- NestJS · Prisma · PostgreSQL · Next.js · Daraja
- Live demo
The ticket is not the problem. The door is.
Selling a ticket online is a solved problem. What is not solved, at a Kenyan gate, is proving that the QR code being held up has not already walked through, been forwarded to four friends, or been screenshotted from someone else’s phone.
Meanwhile the seat that someone is halfway through paying for must not be sold twice — and an abandoned checkout must give the seat back rather than hold it forever.
Ticketing is an inventory problem with a security problem bolted to the exit.
A hold, a payment, a signature, a scan.
Inventory is committed in stages, and the ticket that results is a signed object rather than a row someone could photograph their way past.
Choosing a ticket takes it out of inventory for a bounded window, not forever.
Payment via M-Pesa, with the hold released automatically if it is abandoned.
The ticket is signed with a server-held key, so a forged or edited code fails verification.
The buyer gets a ticket tied to their order, with a per-ticket code.
The gate validates the signature, then burns the code.
A second presentation of the same code is rejected, and the door operator is told why.
The organiser sees admissions as they happen rather than as a total afterwards.
Decisions worth defending.
Tickets are signed, not merely random. A signing key held by the server means validity can be checked without trusting the code itself, so a forged or edited ticket fails before it reaches the turnstile.
Holds expire. A configurable TTL on reservation is the difference between an event that sells out and an event that appears to sell out.
Roles are separate surfaces. Platform admin, organiser and gate operator are distinct — the person on the door gets a scanner, not a dashboard with a refund button on it.
This deployment refuses to start insecurely. The API validates its own configuration at boot and exits if a callback token is still at its default. It stopped me from deploying it wrong, which is exactly what that check is for.
Discovery, then the door.
A public site for finding events, an organiser console behind it, and a gate view built to be operated at arm’s length.
events.brianomariba.com — public discovery, filtered by city and category. Organiser and gate surfaces sit behind sign-in.
What the system changes.
The gate gets a definite answer instead of a judgement call, and the organiser gets a live count instead of a reconciliation the next morning.
This instance is a live demo with seeded events. Payment credentials are deliberately not configured on it — the M-Pesa keys in the source are live, and a public demo is the wrong place for them.