Skip to main content
GatePass provides comprehensive event management tools for organizers to create events, configure ticket tiers, set pricing, and monitor sales in real-time.

Overview

Event organizers can create and manage events through the GatePass platform with full control over ticketing, pricing, venue details, and access controls.

Key Features

Event Types

Support for Physical, Virtual, and Hybrid events

Ticket Tiers

Multiple pricing tiers with different benefits and availability

Sales Management

Configure sale periods, capacity limits, and pricing strategies

Real-time Analytics

Track ticket sales, revenue, and attendee demographics

Event Model

Events are stored in the database with comprehensive metadata:
schema.prisma
model Event {
  id          String   @id @default(cuid())
  title       String
  description String?
  venue       String
  address     String?
  city        String?
  country     String?
  latitude    Float?
  longitude   Float?
  source      String   @default("gatepass")
  externalUrl String?
  
  // Dates
  eventDate   DateTime
  saleStart   DateTime
  saleEnd     DateTime
  
  // Ticket info
  totalSupply    Int
  ticketPrice    Float // Support crypto decimals
  currency       String  @default("ETH")
  maxPerWallet   Int     @default(5)
  
  // Blockchain
  contractAddress String?
  chainId         Int     @default(137) // Polygon
  
  // Metadata
  imageUrl       String?
  metadataUri    String?
  category       String @default("MUSIC")
  tags           String // Comma separated tags
  
  // Settings
  isPublic       Boolean @default(true)
  allowTransfers Boolean @default(true)
  requireKYC     Boolean @default(false)
  
  // Status
  status         String @default("DRAFT")
  
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  // Relations
  organizer   User     @relation(fields: [organizerId], references: [id])
  organizerId String
  
  orders      Order[]
  tickets     Ticket[]
  checkIns    CheckIn[]
  analytics   EventAnalytics[]
  tiers       TicketTier[]

  @@map("events")
  @@index([eventDate, category])
  @@index([latitude, longitude])
}

Creating Events

Event organizers can create events through the API:
router.post(
  '/',
  authenticate,
  requireOrganizer,
  asyncHandler(async (req: AuthenticatedRequest, res) => {
    const body = req.body
    const title: string = body?.title
    const description: string | undefined = body?.description
    const venue: string = body?.venue
    const address: string | undefined = body?.address
    const city: string | undefined = body?.city
    const country: string | undefined = body?.country
    const latitude: number | undefined = body?.latitude
    const longitude: number | undefined = body?.longitude
    const startDateIso: string | undefined = body?.startDate
    const endDateIso: string | undefined = body?.endDate
    const categoryRaw: string | undefined = body?.category
    const tiers: Array<{
      name: string
      description?: string
      price: number
      quantity: number
      maxPerPerson?: number
    }> = Array.isArray(body?.tiers) ? body.tiers : []

    if (!title || !venue || !startDateIso) {
      throw createError('Missing required fields: title, venue, startDate', 400)
    }

    const startDate = new Date(startDateIso)
    const endDate = endDateIso ? new Date(endDateIso) : new Date(startDate.getTime() + 2 * 60 * 60 * 1000)

    const categoryMap: Record<string, string> = {
      conference: 'CONFERENCE',
      concert: 'MUSIC',
      workshop: 'WORKSHOP',
      networking: 'MEETUP',
      festival: 'FESTIVAL',
      sports: 'SPORTS',
      theater: 'THEATER',
      other: 'OTHER',
    }
    const category = (categoryMap[(categoryRaw || '').toLowerCase()] || 'OTHER') as any

    const totalSupply = tiers.length 
      ? tiers.reduce((sum, t) => sum + Number(t.quantity || 0), 0) 
      : Number(body?.maxCapacity || 0) || 0
    const minPrice = tiers.length 
      ? Math.min(...tiers.map((t) => Number(t.price || 0))) 
      : Number(body?.ticketPrice || 0) || 0

    try {
      const event = await prisma.event.create({
        data: {
          title,
          description,
          venue,
          address,
          city,
          country,
          latitude,
          longitude,
          eventDate: startDate,
          saleStart: new Date(),
          saleEnd: endDate,
          totalSupply,
          ticketPrice: minPrice,
          currency: 'USD',
          contractAddress: body?.contractAddress || null,
          chainId: body?.chainId || 137,
          imageUrl: body?.image || null,
          metadataUri: body?.metadataUri || null,
          category,
          tags: '',
          isPublic: true,
          allowTransfers: true,
          requireKYC: false,
          status: 'PUBLISHED',
          organizerId: req.user!.id
        }
      })
      
      // Create ticket tiers
      for (const t of tiers) {
        await prisma.ticketTier.create({
          data: {
            name: t.name,
            description: t.description,
            price: t.price,
            availableQuantity: t.quantity,
            maxPerPerson: t.maxPerPerson || 5,
            saleStart: startDate,
            saleEnd: endDate,
            eventId: event.id
          }
        })
      }
      
      return res.status(201).json({ event })
    } catch {
      throw createError('Failed to create event', 500)
    }
  })
)

Event Categories

GatePass supports various event categories:
Concerts, festivals, and live music performances

Ticket Tiers

Ticket tiers allow organizers to offer multiple pricing options with different benefits:
schema.prisma
model TicketTier {
  id               String   @id @default(cuid())
  name             String
  description      String?
  price            Float
  availableQuantity Int
  maxPerPerson     Int      @default(5)
  saleStart        DateTime
  saleEnd          DateTime
  createdAt        DateTime @default(now())
  updatedAt        DateTime @updatedAt

  // Relations
  event   Event   @relation(fields: [eventId], references: [id])
  eventId String

  @@map("ticket_tiers")
  @@index([eventId])
}

Tier Configuration Examples

{
  name: "Early Bird",
  description: "Save 30% with early bird pricing",
  price: 70,
  availableQuantity: 100,
  maxPerPerson: 2,
  saleStart: new Date('2024-01-01'),
  saleEnd: new Date('2024-02-01')
}
Time-limited discount pricing to incentivize early purchases.
{
  name: "VIP Pass",
  description: "Backstage access, meet & greet, premium seating",
  price: 500,
  availableQuantity: 25,
  maxPerPerson: 1,
  saleStart: new Date('2024-01-01'),
  saleEnd: new Date('2024-05-01')
}
Premium tier with exclusive perks and limited availability.
{
  name: "Group of 5",
  description: "Buy 5 tickets, save 20%",
  price: 400, // $80 per ticket
  availableQuantity: 50,
  maxPerPerson: 10,
  saleStart: new Date('2024-01-01'),
  saleEnd: new Date('2024-05-01')
}
Discounted pricing for bulk purchases to encourage group attendance.
{
  name: "Student Ticket",
  description: "Discounted pricing for students (ID verification required)",
  price: 50,
  availableQuantity: 150,
  maxPerPerson: 1,
  saleStart: new Date('2024-01-01'),
  saleEnd: new Date('2024-05-01')
}
Special pricing for specific demographics with verification requirements.

Managing Events

Viewing Organizer’s Events

events.ts
router.get(
  '/my-events',
  authenticate,
  requireOrganizer,
  asyncHandler(async (req: AuthenticatedRequest, res) => {
    const events = await prisma.event.findMany({
      where: { organizerId: req.user!.id },
      include: { tiers: true },
      orderBy: { createdAt: 'desc' }
    })
    
    const eventsWithStats = await Promise.all(events.map(async (event) => {
      const orders = await prisma.order.findMany({
        where: { eventId: event.id, paymentStatus: 'COMPLETED' }
      })
      const ticketsSold = orders.reduce((sum, o) => sum + o.quantity, 0)
      const revenue = orders.reduce((sum, o) => sum + Number(o.totalAmount), 0)
      
      const lowestTier = event.tiers.length 
        ? event.tiers.reduce((a, b) => (Number(a.price) < Number(b.price) ? a : b)) 
        : null

      return {
        id: event.id,
        title: event.title,
        date: event.eventDate,
        time: new Date(event.eventDate).toLocaleTimeString(),
        venue: event.venue,
        status: event.status.toLowerCase(),
        ticketsSold,
        totalTickets: event.totalSupply,
        revenue,
        ticketPrice: lowestTier ? Number(lowestTier.price) : Number(event.ticketPrice),
        attendees: ticketsSold,
        image: event.imageUrl
      }
    }))

    res.json(eventsWithStats)
  })
)

Event Status Flow

  • DRAFT: Event is being created but not yet published
  • PUBLISHED: Event is visible and tickets are available for sale
  • ACTIVE: Event is currently happening
  • ENDED: Event has concluded
  • CANCELLED: Event was cancelled
  • ARCHIVED: Event data is archived for historical reference

Event Discovery

Users can discover events through search and filtering:
router.get(
  '/',
  asyncHandler(async (req, res) => {
    const q = req.query
    const lat = q.lat ? Number(q.lat) : undefined
    const lng = q.lng ? Number(q.lng) : undefined
    const radiusKm = q.radiusKm ? Number(q.radiusKm) : 50
    const minPrice = q.minPrice ? Number(q.minPrice) : undefined
    const maxPrice = q.maxPrice ? Number(q.maxPrice) : undefined
    const startDate = q.startDate ? new Date(q.startDate as string) : undefined
    const endDate = q.endDate ? new Date(q.endDate as string) : undefined

    let events = await prisma.event.findMany({
      where: {
        isPublic: true,
        status: 'PUBLISHED',
        ...(q.category ? { category: q.category } : {}),
        ...(q.q ? {
          OR: [
            { title: { contains: q.q } },
            { description: { contains: q.q } },
            { city: { contains: q.q } },
            { venue: { contains: q.q } }
          ]
        } : {}),
        ...(startDate || endDate ? {
          eventDate: {
            ...(startDate ? { gte: startDate } : {}),
            ...(endDate ? { lte: endDate } : {})
          }
        } : {})
      },
      include: { tiers: true }
    })

    // Apply price filters
    if (minPrice != null || maxPrice != null) {
      events = events.filter((e) => {
        const lowestTier = e.tiers.length 
          ? e.tiers.reduce((a, b) => (Number(a.price) < Number(b.price) ? a : b)) 
          : null
        const price = lowestTier ? Number(lowestTier.price) : Number(e.ticketPrice)
        
        if (minPrice != null && price < minPrice) return false
        if (maxPrice != null && price > maxPrice) return false
        return true
      })
    }

    // Apply distance filter if location provided
    if (lat != null && lng != null && radiusKm != null) {
      events = events
        .filter((e) => {
          if (e.latitude == null || e.longitude == null) return false
          const distance = haversineKm(
            { lat, lng }, 
            { lat: e.latitude, lng: e.longitude }
          )
          return distance <= radiusKm
        })
        .sort((a, b) => {
          const distA = haversineKm({ lat, lng }, { lat: a.latitude!, lng: a.longitude! })
          const distB = haversineKm({ lat, lng }, { lat: b.latitude!, lng: b.longitude! })
          return distA - distB
        })
    }

    res.json({ events })
  })
)

Event Analytics

Track event performance with detailed analytics:
schema.prisma
model EventAnalytics {
  id        String   @id @default(cuid())
  date      DateTime @default(now())
  
  // Sales metrics
  ticketsSold    Int @default(0)
  revenue        Float @default(0)
  uniqueBuyers   Int @default(0)
  
  // Check-in metrics
  checkIns       Int @default(0)
  checkInRate    Float @default(0) // Percentage
  noShows        Int @default(0)
  
  // Geographic data
  topCountries   String? // JSON string: [{country: "US", count: 10}, ...]
  topCities      String? // JSON string: [{city: "New York", count: 5}, ...]
  
  // Time-based data
  hourlyBreakdown String? // JSON string: Check-in patterns by hour

  // Relations
  event   Event  @relation(fields: [eventId], references: [id])
  eventId String

  @@unique([eventId, date])
  @@map("event_analytics")
}
Analytics are automatically updated as tickets are sold and attendees check in.

Best Practices

Set Realistic Capacity

Configure totalSupply based on actual venue capacity and safety regulations

Configure Sale Periods

Set saleStart and saleEnd to create urgency and manage demand

Use Multiple Tiers

Offer 3-5 ticket tiers to appeal to different audience segments and maximize revenue

Enable Location Search

Always provide latitude and longitude for physical events to enable location-based discovery

Add Rich Metadata

Include high-quality images, detailed descriptions, and relevant tags for better discoverability

Monitor Analytics

Regularly review sales data and adjust pricing or marketing strategies accordingly

Event Settings

Privacy Controls

// Public event - visible to all users
isPublic: true

// Private event - only accessible via direct link
isPublic: false

Transfer Controls

// Allow ticket transfers
allowTransfers: true

// Disable ticket transfers (tickets are soulbound)
allowTransfers: false

KYC Requirements

// Require identity verification for ticket purchase
requireKYC: true

// No KYC required
requireKYC: false

Next Steps

Multiple Payment Methods

Learn how to configure payment options for your events

Ticket Verification

Set up QR code scanning for event check-in

Build docs developers (and LLMs) love