Case study · Engineering
Wooziee: the engineering problems we hit, and how we solved them
Building a ticketing platform is easy until real money, real gates and real crowds arrive. These are the hardest problems we faced building Wooziee for India and the UK, and the decisions that fixed them.
- Team
- 5 engineers, founder-led
- Platforms
- Web, admin, Android, iOS
- Markets
- India and UK
- Scale of build
- ~260 endpoints, 49 models
Challenge 01
Customers paid, but orders still said "unpaid"
The problem
Early on we confirmed orders from the browser's return after checkout. If the customer closed the tab, lost signal or the redirect failed, the money was captured but the order never moved to paid, so no ticket was issued and support had to fix it by hand. Retries could also create the opposite risk: one payment issuing two tickets.
How we solved it
- Made the provider's signed webhook the only source of truth for "paid".
- Made order updates idempotent, so a repeated webhook cannot issue tickets twice.
- Added a scheduled reconciliation against the provider to catch missed events.
- Kept the browser return for user experience only, never for state.
We wrote up the full pattern in webhook-verified payments.
Challenge 02
Multi-day tickets at a busy gate
The problem
Festivals sell passes valid for several days. A simple "scanned = used" flag either blocked a valid pass on day two or let the same pass in twice on the same day. Gate staff also needed an answer in under a second, on mid-range phones.
How we solved it
- Recorded each scan with its event day instead of a single used flag.
- Validated against day-level ticket types and inventory, so a pass is valid for exactly the remaining days.
- Gave organisers a per-day entry history for disputes and headcounts.
- Pushed scans live to the organiser dashboard over Socket.IO.
Challenge 03
Permissions that drifted between API and admin
The problem
Four roles (platform admin, organiser admin, organiser staff and attendee) plus custom staff roles meant permission checks spread across dozens of route modules and screens. When the API and admin UI disagreed, staff saw buttons they could not use, or worse, the API allowed something the UI hid.
How we solved it
- Defined one permission catalogue shared by the API and the admin UI.
- Checked every route against it centrally, including the organisation's status.
- Revoked sessions instantly with JWTs plus a Redis blacklist when roles changed.
Challenge 04
Two countries, two payment providers, one platform
The problem
India runs on Razorpay with Razorpay X payouts; the UK edition needed Stripe. Different currencies, fees, payout flows and webhook formats threatened to fork the codebase.
How we solved it
- Put payments behind a provider-neutral layer with one order and payout model.
- Configured currency, fees and payouts per market, not per code path.
- Normalised each provider's webhooks into the same internal events.
Challenge 05
Event pages search engines could not read
The problem
The React app rendered event details in the browser. Search engines, social previews and AI assistants saw an almost empty page, so shared links looked broken and events did not appear in search.
How we solved it
- Served server-rendered event pages with full details, Open Graph images and structured data.
- Kept the interactive React checkout on top.
- Added automated tests for SEO and AI-crawler access to every release.
Challenge 06
On-sale spikes and slow background work
The problem
When tickets go on sale, traffic arrives at once. Sending emails, push notifications and PDF tickets inside the request made checkout slow exactly when it mattered most.
How we solved it
- Moved email, push, reminders and backups to Bull queues on Redis.
- Cached hot reads in Redis and served media through CloudFront.
- Added rate limiting, Sentry error tracking and New Relic APM to see problems before customers did.
Challenge 07
Handling identity documents safely
The problem
Concession tickets required ID verification, which meant storing sensitive documents that should not stay public or forever.
How we solved it
- Stored documents privately and served them with short-lived signed URLs.
- Logged every view.
- Deleted documents automatically after the event.
Lessons
What we would do again on any platform
- Trust verified server events, never the browser, for money.
- Model real-world events (each scan, each payment) instead of overwriting flags.
- Keep one source of truth for permissions.
- Abstract providers early when more than one market is likely.
- Test SEO, accessibility and crawler access in CI, like any other feature.
Wooziee is live at wooziee.com and wooziee.co.uk. See the project overview for screens and architecture.