Anik Sikder
Blueprints/event-management-ticketing-system

Event Management & Ticketing System

Scalable event management platform supporting event lifecycle management, ticket sales, attendee registration, payment processing, and QR-based access control.

Event ManagementTicketing PlatformSaaSMulti-TenantDjangoRBACPaymentsQR ValidationWorkflow Automation
12 min readJuly 5, 2025
  • Read Time
    12 min read
  • Topics
    9
  • Patterns
    5
  • Level
    Advanced
Architecture Highlights

Concurrency-safe ticket booking

Booking-first reservation model

Webhook-driven payment verification

Event lifecycle state machine

QR-based check-in validation

blueprint.md

$ open blueprint

A ticket sale isn't a normal write operation. For one brief window the moment a popular event goes on sale thousands of people can try to buy the same seat at the same second.

Most systems handle this badly. They sell 500 tickets for a 400-capacity venue, then spend the next three days apologizing over email. The failure isn't in the UI or the payment provider. It's in the architecture's assumption that "check inventory, then reserve it" is a single, safe operation. It isn't not under concurrency.

This platform exists to make that failure structurally impossible, while giving organizers a system that runs an event end to end: creation, sales, payment, and the door.


Executive Summary

Ticketing looks like a CRUD problem until the first high-demand event goes live. Then it becomes a concurrency problem, a payments problem, and a fraud-prevention problem simultaneously.

Business ImpactEngineering Impact
Zero overselling, even at peak demandRow-level locking on inventory during booking
Trustworthy payment reconciliationIdempotent, webhook-driven payment confirmation
Faster event setup and go-liveExplicit lifecycle state machine, no ad-hoc statuses
Reliable, fast entry-gate check-inQR validation that works offline-tolerant at the door
Confidence to run flash sales and dropsReservation holds that expire deterministically

For founders, this is the difference between an event page that can survive a ticket drop going viral and one that falls over or worse, sells tickets that don't exist.

For engineers, this is a textbook case of where "it works in testing" and "it works under real concurrent load" are two completely different claims, and only one of them matters on launch day.


The Business Problem

Picture an organizer launching ticket sales for a 2,000-capacity concert, with 8,000 people refreshing the page at 10:00 AM sharp.

Without the right architecture:

  • Two requests both read "3 tickets left," both proceed, and 4 tickets get sold for 3 seats
  • A payment fails after the ticket was already marked as sold, and now a phantom ticket exists with no paid order behind it
  • A user double-clicks "buy" and gets charged twice, or reserves the same seat twice
  • At the door, staff have no fast, reliable way to confirm a QR code hasn't already been scanned five minutes ago at another gate

Every one of these is a real, common failure mode in ticketing systems that treat booking as "just another database write."


Why Founders Should Care

The Cost of Getting Concurrency Wrong

  • Overselling an event means refunds, reputational damage, and sometimes legal exposure
  • Payment/inventory mismatches mean manual reconciliation after every event
  • A booking flow that buckles under a traffic spike is a launch-day failure, not a bug to fix next sprint
  • Fraudulent duplicate check-ins (screenshotted or shared QR codes) directly cost revenue

The Leverage of Getting It Right

  • Organizers can run genuine flash sales and time-limited drops without fear
  • Payment reconciliation becomes automatic instead of a manual spreadsheet exercise after every event
  • Entry operations move fast because check-in validation is a single, unambiguous lookup, not a judgment call by door staff
  • The platform scales to festivals and multi-day conferences without a rewrite, because the core booking guarantees don't change with volume

Why Engineers Should Care

This system sits at the intersection of three hard problems that don't forgive shortcuts:

  • Concurrency many actors racing for the same finite resource (a seat, a ticket tier)
  • Distributed consistency a payment provider confirms a charge asynchronously, on its own schedule, not the request-response cycle's
  • Idempotency the same webhook, the same retry, the same double-click must never produce two tickets or two charges

Get any one of these wrong and the failure isn't cosmetic it's tickets that don't exist, money that vanished, or a door that lets someone in twice on one QR code.


High-Level Architecture Blueprint

code
                         ┌───────────────────────────┐
                         │   Client Applications     │
                         │ (Web, Mobile, Box Office) │
                         └───────────┬───────────────┘
                                     │
                                     ▼
                         ┌───────────────────────┐
                         │       API Gateway     │
                         └───────────┬───────────┘
                                     │
       ┌───────────────┬─────────────┼─────────────────┬──────────────────┐
       ▼               ▼             ▼                 ▼                  ▼
┌─────────────┐  ┌─────────────┐  ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│   Event     │  │  Booking &  │  │  Payment    │ │  Check-In   │ │  Identity   │
│  Lifecycle  │  │  Inventory  │  │  Service    │ │  & Access   │ │  & RBAC     │
│   Domain    │  │   Engine    │  │             │ │  Control    │ │             │
└──────┬──────┘  └──────┬──────┘  └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
       │                │                │               │               │
       └────────────────┴─────────┬──────┴───────────────┴───────────────┘
                                  ▼
                     ┌──────────────────────────┐
                     │    Shared Service Layer  │
                     │ (Workflow Orchestration) │
                     └─────────────┬────────────┘
                                   │
              ┌────────────────────┼────────────────────┐
              ▼                    ▼                    ▼
       ┌─────────────┐     ┌─────────────┐     ┌───────────────┐
       │ PostgreSQL  │     │ Redis Locks │     │ Webhook Queue │
       │ (Bookings,  │     │ (Reservation│     │ (Payment      │
       │  Tickets)   │     │  Holds)     │     │  Events)      │
       └─────────────┘     └─────────────┘     └───────────────┘

Core Responsibilities

ComponentResponsibility
Event Lifecycle DomainEvent creation, publishing, capacity, and status transitions
Booking & Inventory EngineConcurrency-safe seat/ticket reservation and release
Payment ServiceCharge initiation, webhook verification, idempotent confirmation
Check-In & Access ControlQR issuance, validation, and entry audit records
Identity & RBACAdmin, Organizer, and Attendee role enforcement
Shared Service LayerOrchestrates booking → payment → ticket issuance as one flow
PostgreSQLSystem of record for events, bookings, and tickets
RedisShort-lived reservation locks and hold expiry

Event Lifecycle State Machine

An event is never just "active" or "not active." It moves through explicit, enforced states and each transition has rules about what's allowed.

code
Draft
  │
  ▼
Published
  │
  ▼
On Sale
  │
  ▼
Sold Out  ──────┐
  │             │
  ▼             ▼
In Progress ← Sales Closed
  │
  ▼
Completed
  │
  ▼
Archived

Why an Explicit State Machine, Not Status Flags

Bad

code
if event.is_active and not event.is_cancelled and event.tickets_left > 0:
    # scattered conditionals reconstructing "can this be booked"

Good

code
if event.status.allows_booking():
    # the state itself knows what it permits

Every status flag you add multiplies the number of combinations someone has to reason about. A state machine makes illegal states like "sold out but still accepting bookings" impossible to represent in the first place.


Booking-First Reservation Workflow

The core insight: never let two requests believe they both got the last ticket. Reserve first, confirm second.

code
Ticket Request Received
        │
        ▼
Acquire Row-Level Lock on Ticket Tier
        │
        ▼
Check Available Inventory
        │
        ▼
Create Reservation Hold (expires in N minutes)
        │
        ▼
Release Lock
        │
        ▼
Redirect to Payment
        │
        ▼
Payment Confirmed?
   ├─ Yes → Convert Hold to Confirmed Ticket
   └─ No  → Release Hold, Return Inventory

Concurrency Safety in Practice

Bad

code
if event.tickets_available > 0:
    event.tickets_available -= 1
    create_ticket()
# Two concurrent requests can both pass the check
# before either decrements   classic race condition.

Good

code
with transaction.atomic():
    tier = TicketTier.objects.select_for_update().get(id=tier_id)
    if tier.tickets_available > 0:
        tier.tickets_available -= 1
        tier.save()
        create_reservation_hold()

select_for_update() forces concurrent requests to queue for the same row instead of racing against a stale read. This single line is the difference between "never oversold" and "oversold under load, worked fine in every manual test."

Reservation Holds, Not Instant Commits

A ticket isn't sold the moment someone clicks "buy" it's held. Holds expire automatically if payment isn't completed in time, releasing inventory back to the pool without anyone needing to intervene.

code
Hold Created: 10:03:00
Expires:      10:08:00
Payment received at 10:07:40 → Hold converts to Ticket
Payment not received by 10:08:00 → Hold released, inventory returned

Payment Processing: Idempotency by Design

Payment providers confirm charges via webhook, on their schedule, not the user's request cycle and webhooks can and do arrive more than once.

The Problem

code
Webhook fires for payment_intent_8842
Network hiccup → provider retries
Webhook fires again for payment_intent_8842

Bad

code
def handle_webhook(event):
    create_ticket(event.booking_id)
    # Called twice → two tickets issued for one payment

Good

code
def handle_webhook(event):
    if PaymentEvent.objects.filter(
        provider_event_id=event.id
    ).exists():
        return  # already processed, safely ignored

    with transaction.atomic():
        PaymentEvent.objects.create(provider_event_id=event.id)
        confirm_booking(event.booking_id)

Why This Matters

Idempotency keys turn "the network is unreliable" from a data-integrity risk into a non-issue. The webhook can fire once, three times, or a hundred times the outcome is identical every time.


QR-Based Access Control

Ticket Issuance

code
Payment Confirmed
        │
        ▼
Generate Unique Ticket Token (signed, non-guessable)
        │
        ▼
Encode as QR Code
        │
        ▼
Deliver to Attendee

Validation at the Door

code
QR Scanned at Gate
        │
        ▼
Verify Signature (not tampered / forged)
        │
        ▼
Check Ticket Status
   ├─ Valid, Unused   → Mark as Checked In, Admit
   ├─ Already Used    → Reject, Flag for Staff Review
   └─ Invalid/Unknown → Reject
        │
        ▼
Write Entry Audit Record

Why Signed Tokens, Not Just Ticket IDs

Bad

code
QR encodes: ticket_id=10293

Trivially guessable and shareable anyone can generate a fake QR with a sequential ID.

Good

code
QR encodes: signed_token (ticket_id + expiry + HMAC signature)

A signed token can be verified offline at the gate without a live database round trip for the signature check itself, and it can't be forged without the signing key.

Preventing Duplicate Entry

The "already used" check must be atomic across every gate at the venue, not just the one that scanned it. Two gates scanning the same shared screenshot within the same second must not both admit the check-in write is the same row-level locking pattern as ticket booking, applied to entry instead of inventory.


Multi-Tenant & Role Model

code
Organization (Organizer Account)
   │
   ├── Events (tenant-scoped)
   ├── Ticket Tiers (tenant-scoped)
   ├── Staff / Door Roles (tenant-scoped)
   └── Financial Records (tenant-scoped)

Role Model

RoleCan Do
AdminFull platform control, cross-organization oversight
OrganizerCreate/manage events, view sales, manage staff
Door StaffScan and validate tickets only no sales or event data access
AttendeePurchase tickets, view own bookings and QR codes

Door staff getting scoped, minimal access matters in practice a lost or compromised staff device should never expose sales data or the ability to issue tickets, only the ability to scan them.


Common Mistakes in Ticketing Systems

Checking Inventory Without Locking It

Bad

code
if tickets_available > 0:
    sell_ticket()

Good

code
with transaction.atomic():
    tier = TicketTier.objects.select_for_update().get(id=tier_id)
    # decrement inside the lock, not after a separate read

Treating Webhooks as Guaranteed Single-Delivery

Bad

code
Assume each webhook call happens exactly once

Good

code
Deduplicate by provider event ID before processing

Issuing Tickets Before Payment Is Confirmed

Bad

code
Ticket created immediately on checkout click,
payment processed afterward

Good

code
Reservation hold on checkout click,
ticket created only after payment confirmation

An unpaid "ticket" that exists in the system is inventory the platform can never sell to someone who would actually pay for it.


Evolution Path

code
Basic Event + Ticket CRUD
        │
        ▼
Booking-First Reservation Model
        │
        ▼
Concurrency-Safe Inventory Locking
        │
        ▼
Webhook-Driven Payment Confirmation
        │
        ▼
QR-Based Access Control
        │
        ▼
Multi-Gate, High-Volume Check-In
        │
        ▼
Real-Time Sales Analytics & Fraud Detection

The booking-first model and row-level locking aren't optional additions for later they need to exist before the first high-demand event, because that's precisely when a naive implementation fails.


Real-World Examples

Eventbrite

Known for making self-serve event creation and ticketing accessible to organizers of any size, with reservation flows built to hold under real demand spikes.

Ticketmaster

Known for operating ticketing at a scale where queueing and inventory-locking aren't optional they're the entire product, given how frequently high-demand drops sell out in seconds.

Cvent

Known for extending ticketing into full event operations registration, check-in, and post-event reporting for large conferences and corporate events.

These platforms share a common thread: the sales page is the easy part. The reservation engine underneath it is what actually gets tested under real load.


What I Would Build Today

If I were building an event ticketing platform today, my starting stack would be:

  • Django + Django REST Framework
  • PostgreSQL with select_for_update() for every inventory-affecting write
  • Redis for short-lived reservation holds and lock coordination
  • Webhook-driven payment confirmation with an idempotency table from day one
  • Signed, offline-verifiable QR tokens for entry
  • An explicit event lifecycle state machine instead of boolean status flags

What I Would Avoid Initially

  • Optimistic "check then write" inventory logic it works in every test and fails on the one day it matters
  • Trusting a payment provider's webhook to arrive exactly once
  • Building a custom queueing/waiting-room system before proving the booking engine itself holds up under load

The features that make a ticketing platform impressive flash sales, instant check-in, live capacity dashboards are only trustworthy if the booking engine underneath them cannot oversell. That has to be correct before anything else is built on top of it.


Key Takeaways

Ticketing is a concurrency problem wearing a checkout-flow costume.

A well-designed event ticketing platform provides:

  • Inventory guarantees that hold under real concurrent demand, not just in single-user testing
  • Payment confirmation that's idempotent by construction, immune to retries and duplicate webhooks
  • An explicit event lifecycle that makes invalid states impossible to represent
  • Entry validation that's fast, offline-tolerant, and impossible to duplicate across gates
  • A foundation organizers can trust with a flash sale on day one, not just a slow, steady ticket trickle
status: blueprint_loaded

$ ls related_articles

status: end_of_file

Related Blueprints

Continue exploring related architecture patterns.

Event Management & Ticketing System

Scalable event management platform supporting event lifecycle management, ticket sales, attendee registration, payment processing, and QR-based access control.

01Concurrency-safe ticket booking
02Booking-first reservation model
03Webhook-driven payment verification
04Event lifecycle state machine
05QR-based check-in validation

FinCore Treasury (Financial Operations & Treasury Platform)

Architecture blueprint for a multi-tenant treasury platform covering bank connectivity, cash management, payment orchestration, reconciliation, approval governance, immutable financial accounting, and event-driven financial operations.

01Multi-bank cash visibility
02Event-driven payment orchestration
03Automated reconciliation engine
04Tiered approval governance
05Immutable financial ledger

AccessCore IAM (Enterprise Identity & Access Management Platform)

Enterprise-grade identity and access management platform designed to centralize authentication, authorization, user lifecycle management, RBAC governance, and organizational security controls.

01Multi-tenant identity core
02Hierarchical RBAC engine
03SSO & token architecture
04Audit-ready security logging
05Delegated administration model