All work
MyTicket
Events · Ticketing

MyTicket

An event and experience marketplace for Oman: vendors list events and experiences, sell tiered tickets, and run QR check-in on event day, while customers discover, book, and pay in OMR across a Next.js web app and a native mobile app.

Two-sided marketplace: vendor portal + customer web + native appQR ticket lifecycle with offline-tolerant check-inAmwalPay — HMAC-signed, webhook-reconciled, OMR settlement
Isometric technical illustration of the MyTicket solution architecture
Problem
Local event and experience organisers in Oman had no single, market-fit platform to sell tickets and run the door. Listing inventory, taking OMR payment, issuing tickets, and validating attendees at the gate were spread across generic tools and manual lists — and nothing handled Arabic-first listings, a local payment gateway, or a trustworthy check-in that survives a flaky venue connection.
Approach
We built MyTicket as a two-sided marketplace in a Turborepo monorepo: a Next.js 16 customer web app, a self-serve vendor portal, and a native Expo/React Native mobile app, all over one Drizzle + Neon Postgres backend with Better Auth (organization plugin) separating customers, vendors, and admins. Vendors create event/experience products with tiered ticket types, fixed-date availability slots with real capacity, custom booking and per-attendee forms, refund policies, payouts, and analytics. Checkout is a guided wizard with a 15-minute inventory hold so tickets aren't double-sold while a payment is in flight. Payment runs through AmwalPay (Oman) with HMAC-SHA256 request signing and a webhook-authoritative reconciliation path that is idempotent — a repeated provider callback is detected by system reference and never double-confirms a booking. Every paid booking explodes into individual tickets, each with a unique QR code and a valid → used → expired lifecycle. The vendor scanner validates tickets at the gate and distinguishes success from already-scanned, invalid, expired, cancelled, and wrong-vendor, writing every scan to an audit log; an offline scan queue persists to local storage and batch-syncs with retry when connectivity returns, so the door keeps moving even with no signal. Confirmations go out by email with an ICS calendar attachment.
Outcome
MyTicket is live in production in Oman on a live AmwalPay merchant account, settling ticket sales in OMR. Vendors run the full lifecycle themselves — list, sell, validate at the gate, reconcile, and get paid out — from the portal, while customers buy on either web or the native iOS/Android app. The QR check-in holds up at the door whether or not the venue has a stable connection, and payment reconciliation is webhook-authoritative so a confirmed booking is confirmed exactly once.
Inside the product
MyTicket product screenshot
MyTicket product screenshot
Engagement scope
  • Two-sided marketplace over one Better Auth model: customers, vendors, and platform admins
  • Vendor portal: products (event/experience), tiered ticket types, fixed-date availability with capacity, custom booking + attendee forms, refund policies, payouts, and analytics
  • Guided checkout wizard with a 15-minute inventory hold to prevent oversell during payment
  • AmwalPay integration — HMAC-SHA256 signing, redirect flow, webhook-authoritative idempotent reconciliation, OMR settlement
  • Per-ticket QR generation and valid/used/expired/cancelled lifecycle with a scan-event audit log
  • Vendor QR scanner with already-scanned / wrong-vendor / expired detection and an offline-tolerant scan queue (local-storage persistence, retry, batch sync)
  • Native Expo / React Native mobile app (iOS + Android) sharing the same backend, with myticket.om deep links
  • Arabic + English, RTL-safe; email confirmations with ICS calendar attachments
Next.js 16TypeScriptReact 19Drizzle ORMNeon PostgresBetter Auth (Organization)AmwalPayExpo / React NativeTurborepo
Visit live site