Help Center
How every feature actually works — for organizers running events and buyers booking tickets — straight from the same reference our own team uses.
Event Creation & Publishing
A short help-doc explaining the end-to-end organizer wizard: how a new event goes from a blank form to a published, bookable listing.
What is this?
The Create Event wizard is the single entry point for building an event — basics, venue, media, artists, schedule, pricing, and a final readiness check before it becomes visible to buyers. Every event starts life here, whether it ends up as a paid ticketed show, a free RSVP, or an online-only event.
Organizer flow
- Click Create Event — the wizard's first step ("Basics": name, description, type, category, city, tags, genre, languages, age limit) creates the event as a draft automatically, about a second after you start typing a title — no need to click anything (Next creates it too, as a fallback, if that auto-create hasn't already fired); the title needs at least 3 characters before the event can be published.
- Venue & Online — mark the event as online (with a join link) or set the event's primary venue name/address.
- Media — upload a poster/banner.
- Artists — add artists/speakers and optional highlight badges.
- Venues & Schedule — add one or more schedules for the primary venue, and optionally add other venues for a touring event, each with its own schedule captured right underneath it (see "Schedules & Ticket Shapes" below); changes here save immediately through their own step, not the wizard's Next button.
- Tickets — set Visibility (Public/Unlisted/Private), GST applicability (see "GST / Tax Engine" below), and add ticket types/pricing (see "Ticket Types & Tiered Pricing" below).
- Review & Publish — a live readiness checklist shows what's still missing, a Preview as a buyer would see it link opens the event exactly as buyers will (see "Draft & Preview Before Publish" below), and Publish event goes live once every check passes. If you're not ready, the event stays saved as a draft under My events.
Rules to know
| Rule | Behavior |
|---|---|
| Draft creation | The event is created (and savable/reopenable at any time) about a second after you start typing a title on step 1 — not only when you click Next, and not only at the end of the wizard |
| Refresh recovery | The current draft's ID and wizard step are kept in the page URL, so reloading the browser mid-wizard resumes at the same step with the same draft's data reloaded, instead of losing progress |
| Readiness checks | At least one active schedule, at least one active ticket type, a category, and a venue (or marked online); Reserved Seating schedules additionally need a fully priced seat map, Timed-Entry schedules need at least one entry slot; every additional (touring) venue also needs at least one active schedule of its own |
| Title | Minimum 3 characters |
| Category | Required; can't contain a comma (prevents a malformed multi-category value being pasted in and breaking the discovery chips); can be picked from the managed list or typed as a new one (see "Event Categories & Tagging" below) |
| Gallery photos | Capped by an admin-configured limit (10 by default) |
| Cloning | An existing event can be cloned into a new draft, keeping the wizard flow but skipping re-entering everything from scratch |
| Deleting a never-published draft | A super admin can permanently delete a draft event that has zero orders, from Admin → Dashboard — irreversible, and blocked once the draft has any order/payment record (see "Draft & Preview Before Publish" below) |
Known limitations (as of this writing)
- The wizard's Review & Publish step shows a full client-side readiness checklist before you try to publish; the Manage page's own Publish button doesn't show that checklist upfront — it only surfaces the same error text from the server after you click it, if something's missing.
- Refresh recovery only covers fields already saved by a previous Next/Save draft click on an earlier step — edits made on the current step before advancing are not autosaved, so a refresh mid-step loses only those, not the rest of the draft.
Where this shows up
- Organizer: Create Event → the multi-step wizard (
/organizer/events/new); the same readiness checks apply again on the Manage page's Publish button (/organizer/events/[id]/manage).
Schedules & Ticket Shapes
A short help-doc explaining how event schedules and ticket shapes work together. Companion to the public "How It Works" page.
What is a "schedule"?
A single event listing can have several date/time occurrences — a play running Fri–Sun, a workshop repeating weekly, a touring show at different venues. Each occurrence is a schedule, and every schedule is sold as its own independent inventory pool: selling out one date never affects another, even when they share the same event, venue, or seat map.
What is a "ticket shape"?
Every schedule also needs to tell buyers how tickets are sold for it. That's the schedule's ticket shape — pick one of three per schedule:
| Shape | What buyers see | Best for |
|---|---|---|
| Reserved Seating | A seat map — buyers pick specific seats, priced by zone | Theatres, concerts, screenings with assigned seats |
| General Admission | A quantity stepper against a capacity pool | Standing shows, festivals, meetups |
| Timed-Entry | A list of entry time windows, each with its own capacity | Museums, exhibitions, walk-through experiences |
You choose the shape separately for each schedule, so one event can mix them — e.g. a Friday show with assigned seats and a Saturday show sold as general admission.
Setting it up
- Open your event and go to the Venues & Schedule step (or the Schedules section on the Manage page). The primary venue's block is always shown first.
- Add your first schedule under the primary venue: a start and end date/time.
- Running a touring event? Click Add another venue to add a second (or third, etc.) venue name/address/city. Each venue gets its own block with its own schedule-adding form directly underneath it — a schedule is always added under the specific venue it belongs to, not picked from a dropdown after the fact, and a venue's own date range is no longer entered directly: it's automatically the earliest-to-latest span of its own schedules.
- Add as many schedules as you need under any venue block. Each one can:
- Inherit the event's default ticket pricing, or
- Override it — its own ticket categories and capacity, scoped to that one schedule.
- Use the Ticket shape dropdown on that schedule to pick Reserved Seating, General Admission, or Timed-Entry.
- Depending on your choice:
- Reserved Seating — click Build seat map to lay out floors, sections, rows, and seats, then attach a ticket type (price) to every zone. The seat map is shared by any of the event's schedules that use Reserved Seating, so you only need to build it once even if several schedules reuse the same venue.
- General Admission — enter a Total capacity for that schedule. Leave it blank for unlimited. This is a pool shared across every ticket type sold on the schedule — see "General Admission capacity pool" below.
- Timed-Entry — add one or more entry slots (start time, end time, capacity). You'll need at least one before you can publish.
Rules to know
Schedules & inventory
| Rule | Behavior |
|---|---|
| Inventory | Each schedule has its own ticket/seat pool — selling out one doesn't affect others |
| Pricing | Optional per-schedule override; falls back to the event's default pricing |
| Venue | Fixed to whichever venue block a schedule was added under (the primary venue, or a specific touring venue) — not movable to a different venue afterward; only removing that venue reassigns its schedules back to the primary venue |
| Editing date/time | Allowed via Edit date/time on the schedule any time before it has sold a ticket; locked by the API once at least one ticket is sold for it |
| Deleting a schedule | A never-published (draft) event's schedules can be deleted outright instead of only cancelled, since nothing could have sold against them yet; once published, removing a schedule is always a soft cancel |
| Additional venue | Must have at least one active schedule of its own before the event can be published |
| Schedule window | End must be after start — enforced server-side, not just by the date picker |
| Cancel one schedule | Hides it from buyers immediately; does not auto-refund existing orders for it |
| Cancel last schedule | Blocked — the whole event must be cancelled instead (see "Event Cancellation (Full Event)" below) |
| Past schedules | Auto-hidden from the buyer picker once their end time passes; still visible to organizers for reporting |
- Cancelling a schedule is separate from cancelling the whole event: it only hides that date from buyers and does not touch existing orders or trigger refunds. Full-event cancellation does auto-refund every paid order — see "Event Cancellation (Full Event)" below for what that involves, including a current gap worth knowing about before you rely on it.
- You can't cancel the only remaining active schedule of a published event.
Ticket shapes
- One schedule, one shape. A single schedule can't mix seat picking and quantity selection — but different schedules on the same event can each use a different shape.
- Reserved Seating needs a priced seat map before publishing. Every seat in the map must have a ticket type attached.
- Timed-Entry needs at least one entry slot before publishing.
- Once tickets have been sold for a schedule, its ticket shape is locked — cancel and recreate the schedule instead of changing shape mid-sale.
- Timed-Entry is set up the same way as the others, but online booking for it is coming soon — buyers currently see a "coming soon" notice for those schedules.
General Admission capacity pool
- A schedule's Total capacity is shared across every ticket type sold on it. Selling 80 "Regular" tickets against a 100-capacity pool leaves only 20 total, however buyers choose to split them across whatever types are still open — even types with no per-type cap of their own.
- Ticket types can also set their own quantity cap (see the Ticket Types & Tiered Pricing guide). That narrows things further for that one type, but can never let total sales for the schedule exceed the shared pool. Whichever limit is tighter — the pool or the type's own cap — is what buyers actually see and can book against.
- While a buyer is checking out, their requested quantity counts against the pool immediately, before payment finishes — other buyers see reduced availability during that window, not just after a sale completes. If a payment stalls or is abandoned, that hold clears automatically after a few minutes and the stock becomes bookable again.
- Once the buyer clicks Pay, they see the same live countdown seated bookings get ("Complete payment within 9:54") for that same window — so it's clear their reservation on those tickets is time-limited too, not just seated holds. In the rare case someone else's order claims the last ticket in that window right as this payment completes, the order is automatically refunded and the buyer notified — a captured payment is never left without a ticket issued.
- The Pricing panel shows an inline amber warning if a schedule's active ticket types' quantity caps add up to more than the schedule's own Total capacity — a reminder that the shared pool still wins either way, so some individual caps may never be fully reachable.
Where this shows up
- Organizer: the Venues & Schedule step of the event wizard, the Edit Event page, and the Manage page all show the same venue-first controls. The primary venue's block always comes first, followed by one block per additional (touring) venue; each owns its own schedule list — add, edit date/time, override pricing, set ticket shape, and cancel/delete — from any of the three places.
- Buyer: for an event at a single venue, the event page shows a date/time picker whenever it has more than one open schedule; selecting a date updates the time and prices shown. For an event that runs at more than one venue, the event page instead shows the event-wide date range and an "onwards" starting price, and picking the actual venue and date happens on a dedicated Venue step at the start of the booking page — venues are grouped by city, each with its own date range/price summary, a "Fast Filling" badge when applicable, and a "Know more" popup with the address and a Google Maps link; a venue with more than one date reveals a row of date chips to pick from. Only current and upcoming (non-cancelled) schedules are offered either way, and the choice carries through to the right booking flow (seat map, quantity stepper, or the Timed-Entry placeholder) — a booking is always tied to the specific schedule the buyer picked.
Ticket Types & Tiered Pricing
A short help-doc explaining how named ticket types (Early Bird, Regular, VIP, Student, etc.) work.
What is a "ticket type"?
A ticket type (a.k.a. price category) is a named, priced way to book a schedule — e.g. "Early Bird ₹499", "VIP ₹1,999". A schedule can sell through several ticket types at once, each with its own price and quantity cap.
Setting it up
- On the Pricing step of the event wizard, or the pricing panel on the Edit/Manage page, click Add.
- Enter a name, price, and an optional quantity cap (leave blank for unlimited).
- Click Show sale window, order limits & visibility to optionally set:
- Sale starts / Sale ends — the type auto-switches between Coming Soon → On sale → Sale ended, no manual toggling needed.
- Min / Max per order — e.g. cap VIP to 6 per order while Regular allows more.
- Hidden — keep a comp/sponsor type off the public listing (see "Hidden ticket types" below).
- Repeat for as many types as you need.
- Types added without picking a schedule apply as the event-level default, used by any schedule that doesn't define its own. Types added from a specific schedule's pricing panel override the default for that schedule only.
- Use Disable to pull a type off sale entirely, or Enable to bring it back. Use the Rules link on an existing type to edit its sale window, min/max, or hidden flag after creation.
- All Days Pass — mark a type as valid across every date/venue of a multi-date event, instead of a single schedule.
Rules to know
| Rule | Behavior |
|---|---|
| Scope | Event-level default, or overridden per schedule |
| Visibility | Buyers only see types marked active and not hidden; inactive ones never appear |
| Sale window | Optional per-type start/end date-time; outside the window the type shows Coming Soon / Sale ended and can't be checked out |
| Availability | Buyers see a live remaining count — the tighter of the type's own cap and the schedule's shared General Admission pool (see the Schedules & Ticket Shapes guide), both counting tickets already paid for and tickets in someone else's in-progress checkout, not just completed sales — "Sold out" and "Fast Filling" reflect real, live stock |
| Sold out | Once a type's remaining stock hits zero, checkout for it is blocked — other types stay bookable |
| Order limit | Platform-wide default of 10 tickets per order, unless a type sets its own min/max — those are enforced per type, on top of the platform cap |
| Renaming | Safe at any time — renaming a type doesn't affect tickets already sold under it |
Hidden ticket types
Mark a type Hidden to keep it off the public listing entirely — useful for sponsor or speaker comps. A hidden type is only reachable two ways:
- Direct link — the pricing panel's Copy link button gives a link like
/events/<id>?cat=<ticketTypeId>; opening it reveals that one hidden type (and carries through into the booking flow). - Promo code — a Coupon can be tied to a hidden type; redeeming that code on the event/booking page reveals it, in addition to any discount the coupon applies.
Everything else about a hidden type — price, sale window, min/max, capacity — works exactly like a public one.
Where this shows up
- Organizer: the Pricing step of the event wizard and the pricing panel on the Edit/Manage page (via the schedule panel) — add, rename, edit rules, enable/disable, and toggle All Days Pass from either.
- Buyer: the event page and booking page list active, non-hidden ticket types with price and live availability, scoped to whichever schedule the buyer selected. A "Have an access code?" field on the booking page reveals hidden types by promo code.
Custom Registration Form Builder
A short help-doc explaining organizer-defined checkout fields — collect exactly the info you need from attendees, beyond the standard name/email/phone.
What is a "custom field"?
Each ticket type can carry its own set of extra checkout questions — Short text, Long text, Dropdown, Checkbox, Number, or File upload — used to collect things like dietary needs, company name, T-shirt size, or an ID photo. Every field is marked required or optional, and scoped as per attendee (repeats for every ticket of that type in the order) or per order (asked once, no matter how many tickets).
Setting it up
- On the Pricing step of the event wizard, or the pricing panel on the Edit/Manage page, click Fields on the ticket type you want to configure.
- Click Add field and set:
- Label — the question shown to the buyer.
- Type — Short text, Long text, Dropdown, Checkbox, Number, or File upload. Dropdown needs at least one option (one per line).
- Required — blocks checkout until answered.
- Scope — Per attendee (repeats per ticket) or Per order (asked once per purchase).
- Add as many fields as you need, then click Save fields. Each ticket type has its own independent set — a VIP type can ask different questions than a General type.
- Buyers see the form on the Review step of checkout, right after picking quantity or seats — required fields block Pay until answered.
Rules to know
| Rule | Behavior |
|---|---|
| Scope | Per-attendee fields render once per ticket in the cart; per-order fields render once regardless of ticket count |
| Storage | Every ticket's holder record carries both its own per-attendee answers and a copy of the order's per-order answers |
| Validation | Enforced server-side at checkout, not just in the browser — required fields, dropdown options, and number formatting are all checked before an order is created |
| "This is me" | A shortcut checkbox on one attendee block prefills that ticket's Name from the buyer's account — it never auto-fills custom fields |
| File upload | Uploads immediately when the buyer picks a file, the same way event photos and logos do; the field then holds the resulting URL |
| Editing fields later | Renaming a field after tickets are sold doesn't lose past answers — each field keeps a stable internal ID under the hood |
| No fields configured | Checkout shows no extra form at all — the flow behaves exactly as it did before this feature existed |
Where this shows up
- Organizer: the Fields button next to Rename/Rules/Disable on each ticket type (Pricing step or Manage page pricing panel) opens the field editor. Collected answers show up on the Tickets & Delivery page — toggle Show custom answers to see them inline, or use Download all Excel to export them as extra columns.
- Buyer: an Attendee details section appears on the checkout Review step, between the ticket summary and the price breakdown, whenever the ticket type(s) in the cart have custom fields configured.
Promo / Discount Codes
A short help-doc explaining coupon codes: how organizers set them up and how buyers redeem them at checkout.
What is a coupon?
A coupon is an organizer-issued code that discounts an order — a flat amount off (e.g. ₹100 off) or a percentage off (e.g. 20% off). The same code can also be tied to a hidden ticket type to reveal it (see "Hidden ticket types" above) — discounting and unlocking are independent, a coupon can do either, both, or neither.
Setting it up
- On the Manage page, open the Coupons section and click Add.
- Enter a code, a discount type (Percent or Flat ₹), and a value.
- Optionally set a total usage limit (how many times the code can be redeemed across all buyers).
- Click Show validity window, per-buyer limit & ticket restrictions to optionally set:
- Valid from / Valid to — the code only works inside this window; leave blank for no start/end.
- Max redemptions per buyer — caps how many times the same buyer can redeem it, on top of the total usage limit.
- Restrict to ticket types — check one or more ticket types to limit the discount to those; leave all unchecked to apply it to every type.
- Unlocks hidden ticket type — pick one of the event's hidden ticket types to reveal when this code is redeemed (only shown if the event has hidden types).
- Use Disable/Enable to pull a code off use or bring it back — coupons are disable-not-delete, so past orders that used a code keep showing it.
Rules to know
| Rule | Behavior |
|---|---|
| Discount | Flat ₹ or Percent, computed off the order subtotal, capped so it never exceeds the subtotal |
| Total usage limit | Enforced atomically at payment confirmation — two buyers racing for the last redemption can't both succeed; the loser's order fails and is auto-refunded |
| Per-buyer limit | Optional, on top of the total limit; enforced the same atomic way |
| Ticket-type restriction | Optional; when set, the code only discounts orders for the checked ticket type(s) — leaving none checked means it applies to all |
| Validity window | Optional start/end; outside the window the code is treated as invalid |
| Minimum order | Optional; the code only applies once the subtotal reaches it |
| Invalid/expired code | Shows a clear inline reason at checkout and simply doesn't apply a discount — it never blocks the buyer from checking out without it |
| Hidden-type unlock | Independent of the discount — a code can reveal a hidden ticket type with or without also discounting the order |
Where this shows up
- Organizer: the Coupons section on the Manage page — add, view usage vs. limits, and enable/disable.
- Buyer: a "Have a coupon?" field at checkout validates the code live and shows the discount or the reason it didn't apply, without blocking checkout either way. If the code also unlocks a hidden ticket type, entering it earlier (on the event or booking page's access-code field) reveals that type in the picker before the buyer gets to checkout.
Free Tickets / RSVP
A short help-doc explaining ₹0 ticket types — used for RSVP-style events, guest lists, or comp tickets.
What is a "free ticket"?
Any ticket type (see "Ticket Types & Tiered Pricing" above) becomes a free/RSVP ticket simply by setting its price to ₹0. No separate toggle is needed — it's the same ticket type flow, just priced at zero.
How it behaves
| Rule | Behavior |
|---|---|
| Payment screen | Skipped entirely — an order that totals ₹0 confirms immediately, no gateway redirect |
| Ticket issuance | Identical to a paid ticket — QR ticket generated and delivered (email/WhatsApp) the same way |
| Capacity | Still enforced — a free ticket type's quantity cap and sale window work exactly like a paid one |
| Registration fields | Buyers can still be asked for name/email/phone/group size at checkout, same as any booking — and any custom fields configured on the type (see "Custom Registration Form Builder" above) apply the same way to free tickets |
| Organizer cancellation | Cancelling a free ticket voids it and frees the capacity — there's no refund step since no payment was ever taken |
| Mixing with paid types | A schedule can offer free and paid ticket types side by side; only orders that net to ₹0 skip payment |
Setting it up
- Add a ticket type as usual (Pricing step or Manage page pricing panel).
- Enter 0 as the price.
- Set a quantity cap if you want to limit RSVPs; leave it blank for unlimited.
- Publish — buyers booking this type go straight from selection to confirmation, no payment step shown.
Known limitations (as of this writing)
- There's no dedicated "pay what you want / donation" mode yet — buyers can't choose their own amount above ₹0; the price is fixed per ticket type.
- Free-order cancellations currently reuse the same status label as a paid refund internally, even though no money moved.
See 13-Feature-Gaps-Tracker.md for the tracked list of what's missing.
Where this shows up
- Organizer: same Pricing step / Manage page pricing panel as any other ticket type — just price it at 0.
- Buyer: the event/booking page shows it like any other ticket type; selecting it and confirming skips straight to the ticket confirmation screen, no payment gateway shown.
Event Categories & Tagging
A short help-doc explaining how organizers classify events and how that classification drives buyer-side discovery.
What is a "category" vs. a "tag"?
Every event belongs to one event type (Event, Sport, or Activity), and within that type picks one primary category — e.g. Comedy Shows, Workshops, Running, Adventure. The category field is a combobox backed by a managed, admin-editable list scoped to the event's type, so discovery stays consistent — but an organizer isn't limited to that list: typing a category name that isn't there yet offers a "+ Add ..." option, and picking it both sets that category on the event and adds it to the shared taxonomy (with no suggested tags yet) so it shows up as a normal option for every organizer from then on. An admin can still edit the list directly (see "Setting it up" below) — the two ways of growing the list (admin edit, organizer typing a new one) feed the same underlying list.
Alongside the category, an organizer can add any number of secondary tags (e.g. "Stand-up Comedy", "Poetry", "Outdoor") for finer filtering. Tags are suggestions, not a fixed list — the suggestions are scoped to whichever category is selected, but an organizer can also type a custom tag not in the list.
Language and genre are separate dedicated fields, not tags — so a tag is always about the kind of event, never its language.
Setting it up
As an organizer
- On the event wizard, pick the event's type (Event / Sport / Activity) first — the category suggestions depend on it.
- Use the Category field to pick one primary category from the managed list, or type a category that isn't listed and choose the "+ Add "..."" option to create it — either way, the value is saved on the event, and a newly typed one is added to the shared taxonomy for future events too.
- Use the Tags field to add secondary tags — click a suggested tag (based on the category you picked) or type your own and press Enter.
- A category is required before an event can be published.
As an admin
/admin/settings/event-taxonomylets an admin edit the managed category list per event type, the suggested tags per category, and the platform's language/genre suggestion lists — without a code deploy. Organizer-facing dropdowns always read this list live, falling back to shipped defaults if nothing's been customized yet.- Each category row also has an up/down reorder control and a hide/show (eye icon) toggle. A category's position in this list is its buyer-facing chip order on both the homepage's "By category" strip and the
/eventsfilter row; hiding a category removes its chip from both entirely, no matter how much live inventory it has. Hiding doesn't stop organizers from still picking that category when creating an event — it only keeps it out of the buyer-facing browse chips. - The list doesn't only grow through this page — an organizer typing a brand-new category name into the wizard's Category field adds it here too (with no tags yet, at the end of the list for that event type). An admin doesn't need to pre-create a category before an organizer can use it; cleanup (adding tags, reordering, hiding, or fixing a typo) happens after the fact instead.
Rules to know
| Rule | Behavior |
|---|---|
| Category | Required, single-select, scoped to the event's type; pick from the managed list, or type a new one — a newly typed category is auto-added to the shared taxonomy (no tags yet) the moment the event is created/saved, not just kept as free text on that one event |
| New category from an organizer | Added with an empty tag suggestion list; an admin can enrich its tags, reorder it, or hide it afterward from the taxonomy settings page, same as any other category |
| Tags | Optional, multi-value, suggested per category but not restricted to the suggestions |
| Publish gate | An event can't be published without a category set |
| Category rename/edit | Admin-editable at any time via the taxonomy settings page; existing events keep whatever category string they were saved with even if it's later removed from the managed list |
| Chip order | Admin-configurable — a category's position in the taxonomy list (via the up/down controls) is its buyer-facing chip order, on both the homepage and /events |
| Chip visibility | Admin-configurable per category via the hide/show toggle; a hidden category's chip never appears to buyers, regardless of live inventory |
| Chip counts | Live and city-scoped — GET /events/categories?city=&type= only counts currently published/public events in that city (and type, on /events), so a category with zero matching events there simply shows no chip |
Where this shows up
- Organizer: the event wizard's Category/Tags fields, and the admin taxonomy editor at
/admin/settings/event-taxonomy. - Buyer: the homepage shows the buyer's selected city (with a picker) directly above the "By category" strip, and
/eventshas its own category chip row — both pull from the same city/type-scopedGET /events/categoriesendpoint and show a live count on every chip. Picking a category filters the list; opening Filters on/eventsreveals a tag-chip row (scoped to the active type/category) for finer filtering, and the free-text search box also matches against tags. The buyer's chosen city is remembered (no login required) and carried from the homepage into/events, so switching it on either page keeps both in sync. On an event's detail page, its category and tags are shown as badges, and clicking a tag jumps straight to the browse page pre-filtered to that tag. Category still shapes the icon/color badge shown on event cards across discovery.
Known limitations (as of this writing)
- Category does not yet drive any suggested defaults for ticket shape or registration fields when an organizer sets up a new event — that's called out as a nice-to-have in the original spec but isn't wired up. Organizers configure both from scratch regardless of category.
- Tag filtering on the browse page currently matches all selected tags (AND), not any (OR) — there's no toggle between the two.
- No geolocation-based city auto-detect — the buyer's city is chosen manually and persisted in the browser (
localStorage), not tied to their account, so a first-time visit, a logged-out session, or a different device starts on "all cities" until they pick one.
Draft & Preview Before Publish
A short help-doc explaining how organizers build an event incrementally in Draft state and preview it exactly as a buyer would before making it live.
What is "Draft" vs "Published"?
Every event starts out in Draft the moment it's created, and stays there until the organizer explicitly publishes it. A draft can be saved, reopened, and edited any number of times — it's never shown to buyers or included in discovery. Publishing flips it to Published, the only status buyers can browse, search, or book. Unpublishing takes it off sale again (moves it to Paused) without deleting it.
| Status | Buyer-visible? | Bookable? |
|---|---|---|
| Draft | No | No |
| Published | Yes | Yes |
| Paused | No | No |
| Cancelled | No | No |
Setting it up
- Click Create Event — the event is saved as a draft immediately, before any details are filled in.
- Work through the wizard steps (Details, Schedule, Pricing, etc.) at your own pace — each step saves to the same draft, so you can leave and come back any time from the Manage page.
- Click Preview at any point to see the event exactly as a buyer would (see below).
- When ready, click Publish. Publishing runs a readiness check — the event needs at least one active schedule, at least one active ticket type, a category, and either a venue or "online" set, plus a complete priced seat map for any Reserved Seating schedule or entry slots for any Timed-Entry schedule. Anything missing is listed so it can be fixed before publishing.
- Use Unpublish to take a published event off sale (moves it to Paused) without losing it.
Preview mode
Preview renders the buyer-facing event page and the checkout flow using your real, saved data — but nothing you do in preview is real: no seats are held, no orders are created, and the Pay button is disabled.
- Open it from the Preview button on the Manage page, or the Preview checkout button on the event page itself once you're already in preview.
- Both the event page and the booking page show a clear amber "Preview" banner so it's never confused with a live listing.
- You can select tickets or seats and walk all the way through to the Review & Pay step to check pricing, coupons, and any custom registration fields look right — the Pay button is disabled and labeled "Preview only".
- For an event with more than one schedule, switching dates in preview correctly shows that schedule's own pricing (event default or a per-schedule override) — not just whichever categories happened to be first.
- Preview only works for the organizer who owns the event (or an admin) — the preview links require being signed in as that organizer.
Rules to know
| Rule | Behavior |
|---|---|
| Default status | Every new event starts as Draft |
| Draft visibility | Never shown in public discovery/search, and not reachable by a buyer even via a direct link |
| Publish gate | Blocked until schedule, pricing, category, venue, and (if applicable) seat map / entry slots are complete |
| Preview data source | Owner-only endpoints that ignore event status — the same data a buyer would eventually see, but usable for Draft/Paused/Cancelled events too |
| Preview checkout | Reaches the same Review & Pay screen as a real purchase, but skips seat holds and blocks the Pay action |
| Unpublish | Moves a Published event to Paused, not back to Draft — its saved data and history are untouched |
| Permanent delete | A super admin can permanently delete a draft event, but only while it has zero orders/payment records; a draft with any order (even a pending/failed one) can't be deleted this way — irreversible, unlike Unpublish |
Where this shows up
- Organizer: the Preview button and status pill on the Manage page (also shown on the dashboard's event cards); the Publish/Unpublish buttons on Manage and at the end of the event wizard, with a readiness checklist gating Publish.
- Admin: a Delete button next to any draft row on the Admin dashboard (
/admin), enabled only while that draft has no orders — a confirmation dialog spells out that it's permanent before it fires. - Buyer: nothing — Draft, Paused, and Cancelled events never appear in discovery, search, or via a direct link; only Published events are visible or bookable.
Event Edit After Publish
A short help-doc explaining what an organizer can still change once an event is live — and what locks down once tickets have been sold.
What is this?
Publishing an event doesn't freeze it — organizers can keep editing most details from the Event Details page. What's freely editable versus guarded depends on whether tickets have already been sold, since some changes would be unfair to buyers who've already paid.
Organizer flow
- Open the event and go to Event Details (pencil icon in the Manage sidebar) — the edit form covers title, description, category, tags, age limit, languages, genres, city, poster, online/venue settings, visibility, artists, highlight badges, and gallery images.
- Make changes and click Save changes.
- Schedule-specific edits (date/time, ticket shape, capacity) and additional venues are made separately, from the Venues & Schedule section, not this page — see "Schedules & Ticket Shapes" above.
Rules to know
| Rule | Behavior |
|---|---|
| Freely editable, any time | Description, images, artists, tags — and, notably, the event's default venue name/address, which has no sold-ticket guard at all |
| Admission type | Locked once at least one ticket has been sold anywhere on the event |
| Tax settings (taxable toggle, GST rate) | Locked once at least one ticket has been sold anywhere on the event (matches the rule in "GST / Tax Engine" above) |
| Schedule date/time | Editable via Edit date/time on the schedule (Venues & Schedule step or Manage page) any time before that schedule has sold a ticket; locked by the API once tickets are sold for that schedule |
| Schedule ticket shape | Locked once tickets are sold for that schedule |
| Ticket type price | Not actually enforced by the API even after sales, but the Pricing panel doesn't expose a way to edit an existing type's price — only its name. Disable the type and add a new one instead if the price needs to change |
| Buyer notification | None. No change made from this page — including a venue or date change — sends any notification to buyers who already booked |
Known limitations (as of this writing)
- Changing the event-level default venue after tickets are sold is technically unguarded and silent — no warning banner, no confirmation step, no buyer notice.
- There's no built-in way to notify buyers of a material change (date, venue) after the fact — an organizer would need to reach out manually.
Where this shows up
- Organizer: Event Details in the Manage sidebar (
/organizer/events/[id]/edit).
Event Cancellation (Full Event)
A short help-doc explaining what happens when an entire event — not a single schedule, not a single order — is cancelled.
What is this?
Cancelling the whole event is the most drastic of the platform's three cancellation flows. Unlike cancelling a single schedule (hides one date, no refunds — see "Schedules & Ticket Shapes" above) or Organizer-Initiated Ticket/Order Cancellation (voids specific tickets, refund is a manual follow-up — see below), cancelling the whole event marks it cancelled outright and automatically refunds every paid order through the payment gateway.
What actually happens
- The event's status flips to Cancelled immediately — it disappears from discovery (its page still loads read-only for anyone with the link, with booking disabled — see "Event Detail Page" below).
- Every paid order for the event is refunded through the gateway, honoring each order's own snapshotted fee-refund terms (see "Fee Transparency at Checkout" below): ticket price and its tax are always refunded in full; the platform fee and its GST are refunded only if that order's terms marked the fee refundable.
- Every affected order is moved to Refunded status.
- Every buyer gets a real notification naming the event and the reason given.
- The action is logged to the audit trail.
Rules to know
| Rule | Behavior |
|---|---|
| Refund | Automatic, real gateway refund — not a preview or estimate, unlike the per-order cancellation flow below |
| Refund scope | Ticket price + tax always refunded in full; platform fee + its GST refunded only if the order's snapshotted terms allow it |
| Reversibility | None — a cancelled event cannot be reactivated; create a new listing instead |
| Distinguishing from single-schedule cancellation | Cancelling one schedule only hides that date and never refunds; the last remaining active schedule of a published event can't be cancelled on its own for exactly this reason — the event must be cancelled instead |
| Distinguishing from per-order cancellation | Organizer-Initiated Ticket/Order Cancellation (below) voids specific tickets and only shows a refund estimate — the organizer still has to process it manually. Full-event cancellation processes the refund itself |
Known limitations (as of this writing)
- There is currently no button anywhere in the product to trigger this. The cancellation (and its auto-refund) is fully implemented on the backend, but it's reachable only via a direct API call by a super admin today — neither the admin dashboard nor any organizer page has a "Cancel event" control wired up yet. Until that UI ships, treat this flow's behavior as the reference for what buyers are told to expect (e.g. in the Fee Transparency refund policy paragraph), not as something an organizer can self-serve.
- If a refund call fails partway through a large batch of orders, there's no visible retry/resume — some orders may end up refunded and notified while others aren't.
Where this shows up
- Organizer/Admin: no UI entry point today — see Known limitations above.
- Buyer: the cancellation banner described in "Event Detail Page" below, and a refund notification email once processed.
Seat Map Ticketing
A short help-doc explaining Reserved Seating: how organizers build a priced, color-coded seat map and how buyers pick specific seats at checkout.
What is Reserved Seating?
Reserved Seating is one of the three ticket shapes a schedule can use (see "Schedules & Ticket Shapes" above) — instead of picking a quantity, buyers pick specific seats from a visual layout of the venue. Every seat belongs to a zone (a section tied to a ticket type/price category), so price and availability vary seat-by-seat across the map.
Setting it up (organizer)
- On a schedule using Reserved Seating, click Build seat map to lay out floors, sections, rows, and seats (or reuse a saved venue template).
- Attach a ticket type to each section (or override it per seat) — this sets that zone's price and per-order limits.
- On the ticket type's Rules editor in the Pricing panel, optionally pick a zone color — buyers see this color on available seats in that zone, plus a color legend, so zones are distinguishable at a glance instead of only by hovering. Leave it unset and the zone falls back to the default seat color.
- Set Min / Max per order on the ticket type the same way as any other ticket type (see "Ticket Types & Tiered Pricing" above) — this caps how many seats from that zone a single buyer can pick.
- Locked/booked seat colors are a separate, event-wide setting (Manage page → Seat Status Display) — those apply to every zone, while the new zone color only affects available seats so status always stays unambiguous.
Buyer flow
- Open a schedule that uses Reserved Seating — the booking page shows the seat map instead of a quantity stepper.
- Seats render color-coded by zone (available seats) or by status (selected / on hold / booked / blocked); a legend below the map explains both.
- Clicking a seat selects it; clicking again deselects. Selecting past a zone's max per order is blocked client-side with an inline message instead of silently failing later.
- A running total and a chip list of selected seats (with per-seat price) update live as seats are picked.
- Clicking Proceed holds the selected seats for a limited window (10 minutes by default) and moves to the Review & Pay step, where a live countdown ("Seats held for 9:54") shows how much time is left.
- Clicking Pay renews the hold to a fresh full window right as payment starts, so time already spent reviewing the order doesn't eat into the time available to actually complete payment.
- If the hold expires — either the countdown reaches zero, or payment is attempted after it has — the buyer is returned to the seat map automatically with a clear message and a refreshed view of what's actually still available, rather than a dead-end error.
Rules to know
| Rule | Behavior |
|---|---|
| Real-time availability | Seat status (available/held/booked) syncs live across every concurrent buyer over a websocket connection — no manual refresh needed |
| Zone color | Optional per ticket type; only applied to available seats, so held/booked/blocked/selected always keep their fixed status colors |
| Max per order | Enforced both client-side at selection (fails fast with an inline message) and server-side at checkout (authoritative) |
| Seat hold | Acquired when the buyer clicks Proceed; expires after a configurable TTL (10 minutes by default) if checkout isn't completed |
| Early hold release | A seat is released back to availability right away — not left locked for the rest of the TTL — when the buyer deselects it before proceeding, goes back to Select and changes their pick, or leaves the booking page entirely (closes the tab, hits back, or navigates away); the last case is a best-effort request fired as the page unloads |
| Hold countdown | Shown on the Review & Pay step once seats are held, so the buyer isn't surprised by an expiry mid-checkout |
| Hold renewal | Clicking Pay renews the hold to a full fresh window, so review time doesn't shorten the time available to pay |
| Expired hold | Buyer is bounced back to seat selection automatically (at 0:00, or on a failed Pay), with a refreshed seat map and a re-select prompt — this never fires while the payment window is open, so it can't yank the page out from under an in-progress payment |
| Payment-race protection | If a held seat is somehow booked by a competing order in the moments between Pay and payment capture, the order is automatically refunded and the buyer notified — a captured payment is never left without a ticket |
| Seat vs. schedule | The same physical seat map can be reused across multiple schedules of an event — each schedule tracks its own booking status per seat, so selling out one date's seats doesn't affect another's |
Where this shows up
- Organizer: the Build seat map layout builder on a Reserved Seating schedule; the zone color picker inside a ticket type's Rules editor on the Pricing panel; the event-wide locked/booked seat colors on the Manage page's Seat Status Display section.
- Buyer: the booking page's seat map (Step 1) for any schedule using Reserved Seating, and the hold countdown on the Review & Pay step (Step 2).
Payment Gateway Checkout
A short help-doc explaining how a buyer pays for an order, how a payment is confirmed as genuine, and what happens if it fails.
What is Payment Gateway Checkout?
Once a buyer has reviewed their order on the Review & Pay step (see "Seat Map Ticketing" and "Schedules & Ticket Shapes" above), clicking Pay routes them through an integrated payment gateway. Razorpay is live for buyers today; Cashfree exists on the backend but isn't reachable from the booking page yet (see "Known limitations" below). UPI, cards, netbanking, and wallets are all offered inside Razorpay's own checkout modal — there's no separate flow built per payment method.
Buyer flow
- Review the order summary (tickets, tax, and platform fee — see "Platform Fee Display" and "GST / Tax Engine" below).
- Click Pay — this creates the order (status Pending) and opens the Razorpay checkout modal for the total shown.
- Complete payment in the modal, choosing UPI, card, netbanking, or wallet.
- On success, the buyer is taken to the confirmation/tickets screen once the payment is verified.
- On failure — a declined payment, or closing the modal without paying — the buyer sees an inline message and a Retry payment button in place of Pay. The same seats/tickets stay held; there's no need to reselect, as long as the hold hasn't expired.
- If the payment window doesn't visibly open (a blocked pop-up, an ad blocker, or a slow SDK load), a "Didn't see the payment window?" hint appears under the Pay button once an order has been created, pointing the buyer back to the same button instead of leaving them unsure whether their click did anything.
Resuming a payment after closing the tab
If the buyer closes the tab, refreshes, or the browser crashes after Pay has been clicked (an order and a gateway session now exist) but before payment is confirmed, reopening the booking page for that event within the hold window shows an amber "You have a payment in progress" banner with a live countdown and a Resume payment button. Clicking it reopens the payment modal against the same order — no reselecting seats, no new order created. This is tracked per-browser-tab (sessionStorage), so it only offers to resume in the same browser the payment was started in, and disappears once the hold expires, the order is confirmed, or the buyer completes/abandons the resumed attempt.
How a payment gets confirmed
Two independent signals can confirm a captured payment — either is enough to issue tickets, and both are checked before being trusted:
- Gateway webhook (server-to-server) — the primary, authoritative signal, verified against the gateway's own signature.
- Client-side callback — a faster path right after the modal reports success in the browser, verified with its own signature check before tickets are issued.
Whichever arrives first wins; if the other also arrives, it's a safe no-op — a payment is deduped on the gateway's payment ID and is never confirmed (or ticketed) twice. A browser redirect on its own, without a verified signal, is never treated as proof of payment.
If payment fails
- The order and its hold — a seat lock for Reserved Seating, or counted-against-capacity for General Admission — are left exactly as they were. Nothing is consumed, and nothing is released to other buyers, as long as the original hold window hasn't expired.
- Clicking Retry payment reopens the payment modal against the same order — not a new one. No reselecting seats, no losing a spot in a General Admission pool, and no duplicate order left behind from the failed attempt.
- If the hold genuinely expires — the same countdown covered in "Seat Map Ticketing" and "Schedules & Ticket Shapes" above runs out — before the buyer retries, they're returned to selection to choose again, rather than being stuck on a dead order.
- A payment or retry request that hangs for more than 20 seconds is treated as failed with a clear "took too long to respond" message, instead of leaving the button spinning indefinitely — the order and its hold are unaffected either way.
Gateway reconciliation (recovering a dropped webhook or callback)
Both confirmation signals covered above can fail to arrive — a webhook delivery can be dropped, and a buyer's tab can close right after paying but before the client-side callback fires. Without a safety net, the order would eventually be treated as abandoned and its hold released, even though the buyer was actually charged.
- Automatic sweep — every 5 minutes, before the system fails a stale pending order (see "Rules to know" in "Seat Map Ticketing" / "General Admission capacity pool" for what that would otherwise release), it first asks the gateway directly whether that order's payment was actually captured. If so, the order is confirmed and tickets are issued right there, instead of being wrongly released. If the gateway itself can't be reached, the order is left pending and retried on the next sweep — for up to 24 hours — before it's finally failed, so a temporary gateway outage doesn't wrongly release inventory either.
- On-demand check — on the event's Payments page, any order still showing Pending has a Check gateway status button (in both the main order table and the Analytics tab's buckets). An organizer can click it to verify that specific order against the gateway right now — useful when a buyer reports "I was charged but got no ticket" — instead of waiting for the next automatic sweep. It reports what it finds and, if the gateway shows a captured payment the order missed, confirms the order and issues tickets on the spot; it never fails or otherwise changes an order on its own.
Rules to know
| Rule | Behavior |
|---|---|
| Order creation | Happens the moment Pay is clicked, before payment is attempted — the seat/GA hold and countdown are already active while the gateway modal is open |
| Confirmation source of truth | The gateway's signed webhook — a client-side success callback is also accepted, but only after its own signature is verified |
| Idempotency | If both the webhook and the client callback arrive for the same payment, the second is a safe no-op — tickets are issued once per order, never twice |
| Failed payment | Never consumes the ticket or seat — the order stays retryable against the same hold until it expires |
| Retry | Reopens payment on the same order, reusing the same held seats/tickets rather than starting checkout over |
| Resume after leaving the tab | A "Resume payment" banner reopens the gateway modal on the same order from a different visit to the booking page, within the same browser tab/session, as long as the hold hasn't expired |
| Request timeout | A payment/retry request gets 20 seconds to respond before it's surfaced as a failure; the order and hold are untouched |
| Hold expiry during a stalled payment | If the hold's window lapses before payment or retry completes, the order is voided and the hold released automatically |
| Gateway reconciliation | Before the 5-minute stale-order sweep fails a pending order, it checks the gateway directly for a captured payment it might have missed; an organizer can also trigger this check on demand from the Payments page ("Check gateway status") |
| Payment method coverage | UPI, cards, netbanking, and wallets are all available inside the Razorpay checkout modal by default — no separate setup per method |
| Gateway choice | Platform-wide today, set via server configuration — not yet selectable per organizer or per event from any admin screen |
Known limitations (as of this writing)
- Cashfree is implemented on the backend but not reachable by buyers yet — the booking page only opens the Razorpay modal, so an event configured for Cashfree currently can't be paid for.
- Per-organizer gateway selection isn't wired up yet, even though it's modeled — every event uses the same platform-wide gateway.
- No pay-what-you-want/donation flow — see "Free Tickets / RSVP" above for the closest existing option (a fixed ₹0 ticket type).
See 13-Feature-Gaps-Tracker.md for the tracked list of what's missing.
Where this shows up
- Buyer: the Review & Pay step's Pay button (opens the Razorpay modal); after a failed or abandoned attempt, the amber retry banner with its Retry payment button; and, on reopening the booking page after leaving mid-payment, the amber "You have a payment in progress" banner with its Resume payment button — all on the booking page.
- Organizer: the Check gateway status button next to any Pending order on the event's Payments page (
/organizer/events/[id]/payments), in both the main order table and the Analytics tab's buckets. - Admin: no dedicated settings screen yet — the active gateway and its credentials are set at the server/environment level.
Platform Fee Display
A short help-doc explaining the platform convenience fee: how it's set and how buyers see it at checkout.
What is the platform fee?
The platform fee is a convenience fee the platform earns on every paid order, shown as its own line item at checkout — separate from the ticket price. It's platform revenue, not organizer revenue, so organizers can't set or change it; only a super admin configures it. Admin can set one platform-wide default, and optionally give a specific organizer different terms.
Setting it up (admin only)
Platform-wide default
- Go to Admin → Settings and open the Platform Fee section.
- Choose the fee type:
- Percent of subtotal — e.g. 2% of the ticket subtotal, recalculated per order.
- Flat amount (INR) — a fixed amount added to every order regardless of subtotal.
- Set the value for that type.
- Toggle Refund this fee when the organizer/platform cancels an event — controls whether the fee portion is included when a full event cancellation triggers an automatic refund (see "Rules to know" below).
- Write a short refund policy note — this text feeds into a whole-order refund summary the buyer sees at checkout (see Fee Transparency at Checkout below), not just a standalone fee-line caption.
- Click Save Settings. The new fee applies to orders created from that point on; it does not change orders already placed.
Per-organizer override
- Go to Admin → Users, find the organizer, and click Fee override → under their organizer details.
- Set a custom fee type, value, refundability, and refund policy note the same way as the platform-wide default — these apply to every event that organizer runs, instead of the platform default.
- Click Clear override (use platform default) to remove the custom terms and fall back to the platform-wide setting.
- The organizer row shows Fee override (custom) whenever an override is active, so it's easy to spot at a glance.
Buyer flow
- On the booking page's ticket-selection step, the sticky price bar shows an all-in estimate (subtotal + platform fee) so the fee is never a surprise later — it's explicitly labeled as an estimate with the full breakdown one step away.
- On the Review & Pay step, the order summary lists, in order: Subtotal → Tax (if the event is taxable) → Platform fee → GST on platform fee (if enabled) → Discount (if a coupon was applied) → Total, followed by a whole-order refund policy summary covering the ticket price/tax and the fee/its GST together — see "GST / Tax Engine" below for how the two tax lines are computed, and Fee Transparency at Checkout below for the refund summary itself.
- Clicking Pay charges exactly the total shown — nothing is added to the order after this screen.
- The same itemized breakdown and refund summary are shown again on the order's detail page in My Tickets after purchase — see "Order & Ticket Management" below — so the numbers a buyer saw before paying are never lost after paying.
How the money actually flows
The platform fee is added on top of the ticket price — it's an extra charge to the buyer, not a cut taken out of what the organizer priced their tickets at. The buyer's payment (ticket price + tax on the ticket + platform fee + GST on the platform fee − discount) is captured as a single transaction into the platform's own Razorpay/Cashfree merchant account — there's no split-payment/marketplace routing, so an organizer's share isn't auto-transferred anywhere.
Settling an organizer (paying out their share, i.e. everything except the platform fee and its own GST) is a manual, offline step today — it isn't automated by this app. PayoutAccount (bank/UPI details per organizer) is stored but nothing currently reads it to trigger a transfer. The organizer's Payments page (per-event, under Manage — see "Payment & Settlement View" below) is the reference for that: it shows subtotal, fee, tax, total, and refunds per order, plus event-level totals (grossTotal, feesTotal, refundTotal) and a Net Payable figure that already subtracts fees and refunds correctly. It does not yet show the platform's own fee-tax (platformFeeTax) as a separate column, even though that value is persisted on every order — see "GST / Tax Engine" below.
Note: an older
netRevenuefigure (paidTotal − refunds, which does not subtract the platform fee) still exists in the API response for backward compatibility, and is still what the separate Sales Dashboard displays as "Net revenue" — see "Sales Dashboard" below. The Payments page itself has moved on to the corrected Net Payable figure; use that one, notnetRevenue, as the true payable amount.
Rules to know
| Rule | Behavior |
|---|---|
| Who configures it | Super admin only — organizers have no control over it |
| Scope | One platform-wide default (Admin → Settings), plus an optional per-organizer override (Admin → Users → Fee override) that applies to every event that organizer runs |
| Precedence | An organizer's override, when set, always wins over the platform-wide default for that organizer's events |
| Fee type | Either a flat INR amount or a percent of the order subtotal — chosen independently for the platform default and for each organizer override |
| Where it's computed | The same order-creation step that produces the order's persisted total — the amount charged at payment is read from that same row, so it can't drift after the order summary is shown |
| Refund terms snapshot | Each order stores the fee's refund terms (refundable yes/no, policy note) as they applied at checkout time — a later admin change to the platform default or an organizer's override never rewrites what a buyer already saw for orders already placed |
| Full event cancellation | Auto-refunds the whole order, including the fee and the GST charged on the fee, unless the order's snapshotted terms say the fee isn't refundable — in which case both the fee and the GST on it are excluded, and only the ticket price + its own tax is refunded |
| Tax line | Real as of the GST / Tax Engine feature — computed per order and shown whenever it's non-zero; see "GST / Tax Engine" below |
Known limitations (as of this writing)
- Overrides are per-organizer, not per-event — an organizer can't get different fee terms for two different events they run.
- No automated payout/settlement to organizers — see "How the money actually flows" above.
- The Sales Dashboard's "Net revenue" figure doesn't subtract the platform fee, so it overstates the organizer's true payable amount — the Payments page's own Net Payable figure has already been corrected for this; see "Payment & Settlement View" below.
Where this shows up
- Admin: Admin → Settings → Platform Fee section for the platform-wide default; the Fee override → link under an organizer's details on Admin → Users for a per-organizer override.
- Buyer: the sticky estimate on the booking page's ticket-selection step, and the itemized order summary (with refund policy note) on the Review & Pay step — both automatically reflect the organizer's override when that event's organizer has one.
GST / Tax Engine
A short help-doc explaining how tax is calculated on ticket sales, who configures it, and how buyers see it at checkout.
What is the GST / Tax Engine?
Two independent tax events exist on every order, kept separate throughout because they belong to different GSTINs:
- Tax on the ticket price — the organizer's own GST, under the organizer's own GSTIN. The organizer decides whether it applies and at what rate.
- Tax on the platform fee — the platform's own GST on the convenience fee it earns (see "Platform Fee Display" above), under the platform's own GSTIN. Only a super admin configures this, for the same reason only an admin configures the fee itself.
Neither tax is computed on the other's base — ticket tax is never applied to the platform fee, and the platform's fee-tax is never applied to the ticket price.
Setting it up
Organizer: tax on the ticket (per event)
- Open the event in Organizer → Events → Manage/Edit and go to the Settings section (create wizard: the same step as Visibility, in the Tickets step; edit page: the Settings section).
- Check GST applies to tickets for this event to reveal the tax fields.
- Fill in:
- Your GSTIN — the organizer's own GST registration number, snapshotted onto every order placed under this configuration (for future invoicing).
- GST rate (%) — the flat rate applied to each taxable ticket's price.
- Tax-exempt below (₹) — tickets priced at or below this amount are automatically exempt (₹0 tax), so low-priced tickets don't need a manual per-category override.
- Click Save changes. Once tickets have been sold for the event, the taxable toggle and rate can no longer be changed — this prevents retroactively changing what buyers already paid tax on; the exempt threshold and GSTIN can still be edited.
Organizer: per-ticket-type override
On the event's Pricing panel (Ticket categories & pricing), each ticket type has a Tax column, editable via its Rules editor:
- Same as event (default) — inherits the event's taxable setting above.
- Taxable — always taxed at the event's rate, even if the event-level toggle is off.
- Exempt — never taxed, even if the event is taxable overall (e.g. a comp/sponsor ticket type).
This is what lets a single event mix taxable and exempt ticket types — tax is computed per ticket line, not once on the whole cart.
Admin: tax on the platform fee
- Go to Admin → Settings and open the Platform Fee Tax (GST) section.
- Check Apply GST to the platform fee, set the rate (%), and enter the platform's GSTIN.
- Click Save Settings. This is platform-wide only — there's no per-organizer override for the platform's own tax, the same as the platform fee itself.
Buyer flow
- On the Review & Pay step, the order summary shows Tax (GST) — the organizer's tax on the ticket subtotal — right after Subtotal, and (if the admin has enabled it) GST on platform fee right after the Platform fee line. Either line is omitted entirely when it's ₹0, the same way the existing Tax row already behaved before this feature.
- The Total the buyer pays is fully tax-inclusive — ticket price, both applicable taxes, and the platform fee are all captured in a single payment; nothing is added after this screen.
How it's computed
- Each ticket line is priced, then taxed independently:
0if the event/category isn't taxable,0if the ticket's price is at or below the event's exempt threshold, otherwiseprice × rate%, rounded to the nearest paisa. - The order's
taxfield is the sum of every line's tax;platformFeeTaxis computed once, on the platform fee amount, using the admin's rate. - Both values — along with the organizer's and platform's GSTIN and the rate applied — are snapshotted onto the order at checkout time, the same pattern the platform fee's refund terms already use. A later change to an event's tax settings or the platform's fee-tax config never rewrites what a buyer already saw and paid on an order already placed.
Total = Subtotal − Discount + Platform fee + Tax + GST on platform fee.
Rules to know
| Rule | Behavior |
|---|---|
| Who configures ticket tax | The organizer, per event — not gated by admin, since it's the organizer's own tax liability |
| Who configures the platform's fee-tax | Super admin only, platform-wide — mirrors who configures the platform fee itself |
| Taxability precedence | A ticket type's explicit override (Taxable/Exempt) always wins over the event's own toggle; "Same as event" inherits it |
| Exemption | Automatic — any ticket priced at or below the event's exempt threshold is never taxed, regardless of the taxable toggle |
| Where it's computed | The same order-creation step that produces the order's persisted total, computed per ticket line so mixed taxable/exempt carts are handled correctly |
| Snapshot | Rate, both GSTINs, and the resulting tax amounts are frozen onto the order at checkout — later config changes only affect new orders |
| Locked after sales | Once an event has sold tickets, its taxable toggle and GST rate can no longer be changed (the exempt threshold and GSTIN still can) |
| Refunds | Not treated specially — a refund already keys off the order's total, which includes both tax amounts by construction, so a full refund already refunds the tax paid |
Known limitations (as of this writing)
No GSTIN format/checksum validationFixed (2026-09-07) — both the organizer's and the platform's GSTIN are now validated against the real GSTN format and modulo-36 checksum on save (common/gstin.util.ts).The organizer's Payments page showsFixed (2026-09-09) — the Payments page's "Fees" stat breaks out "platform fee" and "GST on fee" separately.taxper order but doesn't yet showplatformFeeTaxas its own columnNo GST invoice/receipt document is generated yetFixed (2026-09-07) — GST Invoice Generation shipped: a per-order PDF invoice is available from the buyer's order-detail page and viaGET /orders/:orderId/invoice.- Only a single flat rate and a single exempt threshold per event — no multi-slab rate table (e.g. different rates at different price bands within the same event).
- This engine covers the organiser's own GST on the ticket and the platform's own GST on its fee — it does not cover the platform's marketplace obligations as an e-commerce operator (Section 52 TCS, Section 194-O TDS on organiser payouts) or any artist/vendor withholding (Section 195 TDS, RCM GST on a foreign performer's fee). None of that is implemented anywhere in the product today; see 15-GST-TCS-TDS-Taxation-Guide.md for the full picture and the "Ticket Price Calculator" section below for the one place TCS/TDS can currently be modeled (as a standalone scenario, not a real deduction).
Where this shows up
- Organizer: the Settings section of the event create/edit pages (event-level tax toggle, GSTIN, rate, exempt threshold); the Tax column in the Pricing panel's Rules editor for a per-ticket-type override.
- Admin: Admin → Settings → Platform Fee Tax (GST) section, for the platform's own tax on its fee.
- Buyer: the itemized order summary on the Review & Pay step — the Tax (GST) and GST on platform fee lines, shown whenever non-zero.
Ticket Price Calculator
A short help-doc explaining the standalone what-if tool for working out ticket price vs. buyer total vs. organizer payout, without touching any real event.
What is this?
The Ticket Price Calculator is a sandbox that runs the exact same fee/tax math as real checkout (see "Platform Fee Display" and "GST / Tax Engine" above) against numbers you type in — it never reads or writes a real event, order, or setting. It answers two different questions depending on which way you work:
- Price → breakdown — "If I price this ticket at ₹500, what does the buyer actually pay, and what do I net?"
- Target buyer price → ticket price — "I want the buyer to pay a round ₹600 all-in — what ticket price do I need to set to land exactly there?"
How it works
- Pick a mode: Price → breakdown (forward) or Target buyer price → ticket price (reverse).
- Enter the one number the mode asks for — a ticket price, or a target buyer-facing total.
- Set the platform fee (type and value), whether tax applies to the fee and at what rate, whether item tax (GST) applies and at what rate, and a tax-exempt-below amount — all four mirror the real settings covered in "Platform Fee Display" and "GST / Tax Engine" above.
- The breakdown below updates live: the same itemized lines a buyer sees at checkout (Subtotal → Tax → Platform fee → GST on fee → Total), plus an Organizer nets figure.
- In reverse mode, the tool solves for the ticket price by search rather than a closed formula (since which tax lines apply can change right at the exempt threshold) — landing on the exact price that produces your target total to the paisa.
- Below that, an Organizer payout withholding (India, marketplace TCS/TDS) section models what an e-commerce operator must actually withhold before paying an organiser out — GST TCS (Sec. 52) and Income-tax TDS (Sec. 194-O) — each with its own toggle and rate, pre-filled at the current statutory rates (0.5% and 0.1%). When either is on, a Net cash to organizer figure appears under Organizer nets, showing what actually lands in the organiser's bank account after both are withheld — see the "Deep-Dive ... + Taxation Guide" (15-GST-TCS-TDS-Taxation-Guide.md) for the worked example this mirrors.
Two entry points, one shared tool
The same calculator UI powers two different pages, each pre-filled from real settings so you're not starting from arbitrary numbers, but with every field still editable to try other scenarios:
- Organizer (
/organizer/ticket-price-calculator) — a Use settings from event dropdown lets you pick one of your own events to pre-fill its real platform fee (including any per-organizer override you have — see "Platform Fee Display" above) and that event's real GST settings. Leaving it on Platform default pre-fills just the platform-wide fee, with item tax off. - Admin (
/admin/ticket-price-calculator) — pre-fills the platform-wide fee and platform fee-tax settings only (Admin → Settings). There's no per-event item-tax pre-fill here, since GST on the ticket itself is an organizer/event setting, not a platform one.
Rules to know
| Rule | Behavior |
|---|---|
| Simulation only | Never creates, reads for saving, or modifies any real event, order, or setting — purely a calculator |
| Forward mode | Enter a ticket price; the tool computes the buyer's total and the organizer's net from it |
| Reverse mode | Enter a target buyer-facing total; the tool searches for the ticket price that produces that exact total, accounting for the fee, both tax lines, and the exempt threshold |
| Reverse mode with an unreachable target | If the fee and tax alone (at a ₹0 ticket price) already meet or exceed the target, the tool shows an inline error instead of a negative or nonsensical price — raise the target or lower the fee to fix it |
| Organizer pre-fill | Picking an event pulls that event's live fee (with any organizer override) and live GST settings; every field stays editable afterward |
| Admin pre-fill | Platform-wide fee and fee-tax settings only; no event-specific item tax |
| Breakdown format | Same line order and labels as the real Review & Pay checkout screen, so a scenario worked out here reads the same way at actual checkout |
| Organizer nets | Ticket price plus any item tax collected on it — not pure profit, since the item-tax portion is money the organizer still owes to the tax authority, not revenue |
| Organizer payout withholding (TCS/TDS) | Simulation only, not wired to any real setting — both toggles default on at the current statutory rates (0.5% TCS, 0.1% TDS), each computed on the net ticket price (excl. GST), matching how they're actually assessed |
| Net cash to organizer | Organizer nets minus TCS minus TDS — what would actually hit the organiser's bank account if payouts were withheld correctly; neither amount is lost to the organiser, since TCS is a GST cash-ledger credit and TDS a Form 26AS credit, both usable against the organiser's own final liability |
Known limitations (as of this writing)
- There's no "apply this price" action — the calculator is read-only; a solved ticket price has to be typed into the event's actual Pricing step by hand.
- Doesn't model a coupon/discount, a min/max-per-order rule, or a quantity cap — it computes one ticket's price/fee/tax math, not a full order or cart.
- The reverse-mode search always looks for a value at or below the target, rounded to the nearest paisa, so the buyer's actual total from that solved price can land up to a paisa under the exact target you typed.
- This calculator's TCS/TDS section is a one-ticket, on-the-fly simulation, separate from the real per-organizer running totals now tracked on the Payment & Settlement View page (see "Marketplace withholding" there) — use this one to explore a hypothetical scenario, and that one for the real, recorded figures against an actual organizer's settlements. Neither one moves real money; settlement to organizers remains the manual, offline step described in "Platform Fee Display" above.
- No tiered/slab GST rates (e.g. movie tickets ≤₹100 at 5% vs >₹100 at 18%, or the ≤₹500 cultural/sporting-event exemption) — only a single flat rate and a single exempt threshold, same limitation as the event-level GST engine above.
Where this shows up
- Organizer: Ticket Price Calculator in the sidebar's Events section (
/organizer/ticket-price-calculator). - Admin: Ticket Price Calculator in the sidebar (
/admin/ticket-price-calculator).
Ticket Delivery (QR + Email + WhatsApp)
A short help-doc explaining how a ticket actually gets to a buyer once an order is paid: QR generation, email, and the current state of WhatsApp delivery.
What is this?
The moment an order is confirmed as paid, the platform mints one QR-coded ticket per ticket line in the order and sends it out. This is the mechanism underneath both the automatic delivery at checkout and the manual Resend buttons covered in "Order & Ticket Management" below.
How it works
- Payment confirmation — via the gateway's webhook, or the client-side success callback (see "Payment Gateway Checkout" above) — is the single trigger. Nothing is issued at order/checkout creation, only once payment is actually confirmed.
- One ticket record is minted per ticket line, each with its own unique QR code (a signed token, not a raw ID — see "Rules to know" below) and its own sequential serial number for that event.
- An email is sent immediately with the PDF ticket(s) attached.
- A WhatsApp send is attempted the same way — but see Known limitations below.
- Tickets are also always available afterward in My Tickets, regardless of whether the original delivery email arrived.
Rules to know
| Rule | Behavior |
|---|---|
| Trigger | Payment confirmation only — not order/checkout creation |
| QR uniqueness | Per ticket, not per order — a signed token unique to that one ticket, verified server-side against a stored hash |
| Fully functional — sent automatically on confirmation, and available anytime via Resend email | |
| Not actually wired to a provider yet — see Known limitations | |
| Regenerating a ticket | An organizer can regenerate a ticket's QR code (e.g. if it leaked); this invalidates the old code and re-emails the new one — by email only |
| Delivery status shown to organizers | Read from the notification log per ticket, not a dedicated delivery-status field |
Known limitations (as of this writing)
- WhatsApp delivery is a silent no-op today. The button and the request both work, and the ticket's delivery log shows it as "sent," but nothing is actually transmitted — no WhatsApp provider is connected on the backend. This applies to automatic delivery, manual resend, and complimentary/bulk-minted tickets alike, so a guest given a ticket by phone number only (no email) currently receives no real delivery at all. See 13 — Feature Gaps Tracker for status.
- Email itself will report success even if the email-sending service isn't configured on a given environment (it logs a simulated send) — worth checking actual inboxes when testing on a new deployment, not just the in-app status.
- WhatsApp is never attempted automatically as a fallback when email fails, or vice versa — the two channels are independent, not failover-linked.
Where this shows up
- Organizer: the Resend button in Tickets & Delivery (
/organizer/events/[id]/tickets) — email only from this screen. - Buyer: automatic delivery right after payment; Resend email / Resend WhatsApp buttons on the order detail page in My Tickets (
/tickets/[orderId]) — see "Order & Ticket Management" below.
Attendee-Level Ticket Records
A short help-doc explaining why a 4-ticket order shows up as 4 independent tickets, not one block.
What is this?
When an order contains more than one ticket — say, 4 seats bought in a single checkout — each ticket is its own independent record: its own QR code, its own holder details, its own check-in status. Nothing about one ticket in the order affects its siblings.
How it works
- Every ticket line in an order becomes its own ticket record, carrying a snapshot of that specific attendee's details (name, email, phone, and any custom registration answers marked "per attendee" — see "Custom Registration Form Builder" above) captured at the moment the ticket is issued.
- Each ticket gets its own QR code and serial number.
- Checking one ticket in at the gate has zero effect on the others from the same order — they remain independently valid until scanned (or cancelled) on their own.
- Organizer-initiated cancellation works the same way: voiding 2 of 5 tickets in an order leaves the other 3 completely untouched (see "Organizer-Initiated Ticket/Order Cancellation" below).
A related but different feature: group tickets
Some events let an organizer enable group-size tickets for General Admission — where one single ticket is allowed to be scanned multiple times (e.g. a "Family pass, admits 4" scans 4 times before it's marked used). This is the opposite mechanic from attendee-level tickets: one ticket admitting several people, versus several tickets each admitting one. Both can exist on the same event — don't confuse "why did the check-in count jump by 4 on one scan" (a group ticket) with "why are there 4 separate ticket cards in my order" (attendee-level tickets, this feature).
Rules to know
| Rule | Behavior |
|---|---|
| Granularity | One ticket record per ticket line in the order, each independently trackable |
| Attendee data | Snapshotted onto the ticket at issuance from that line's registration answers |
| Check-in independence | Scanning/voiding one ticket never changes the status of sibling tickets in the same order |
| Partial cancellation | An organizer can cancel any subset of tickets in an order without touching the rest |
Known limitations (as of this writing)
- There's no way to reassign a ticket to a different attendee after issuance. If plans change and a different person will use one ticket in a multi-ticket order, there's currently no reassign/edit-holder action anywhere — the original attendee details stay on the ticket permanently (regenerating a ticket only replaces its QR code, not its holder details).
Where this shows up
- Organizer: each ticket appears as its own row in Tickets & Delivery and Attendees (see "Attendee List & Export" below), and its own line in the Payments page's order detail.
- Buyer: each ticket appears as its own card (with its own QR) on the order detail page in My Tickets.
Order & Ticket Management
A short help-doc explaining the buyer's "My Tickets" area: where past and upcoming orders live, how to get a ticket back, and how cancelled/refunded orders are shown.
What is "My Tickets"?
My Tickets is the buyer's own order history — every order they've placed, whether it's still pending payment, confirmed, failed, cancelled, or refunded. It's organized around the order (one checkout = one order, which can contain several tickets), not around individual tickets, so a buyer who bought 4 seats in one purchase sees one order card, not four.
Buyer flow
- Open My Tickets from the account menu — this lists every order for the signed-in buyer, most recent first.
- Toggle Upcoming only / All orders to switch between orders for events that haven't happened yet and the full history; optionally narrow further with the event filter.
- Each order card shows the event, venue, date, ticket count, a status badge (Confirmed / Payment pending / Payment failed / Cancelled / Refunded), and the order total.
- Opening an order shows its full detail: event/venue/date, the same status badge, order number and total, an itemized Price Breakdown (subtotal, tax, platform fee, GST on the fee, discount, total) with the same refund policy summary shown at checkout, and one card per issued ticket with its QR code.
- From a ticket card, the buyer can download the e-ticket as PDF, or resend it — to email or WhatsApp — without needing to contact support.
- When an order has more than one ticket, they can be select-all'd and downloaded together as a single combined PDF or a ZIP of individual PDFs.
Rules to know
| Rule | Behavior |
|---|---|
| Unit of the list | The order, not the ticket — multiple tickets from one checkout are grouped under one order card/detail page |
| Status source | The order-level status (pending/paid/failed/cancelled/refunded), not the per-ticket status — a refunded order's tickets still technically read "valid," so the order's own badge is what tells the buyer it was refunded |
| Cancelled/refunded orders stay visible | Never hidden from history — shown with a clear status badge, and the ticket cards underneath still render normally (QR, download) for the buyer's records |
| Orders with no tickets yet | Pending or failed orders (payment never completed) still appear in the list, with an empty ticket section on the detail page — an abandoned or failed checkout isn't silently dropped from history |
| Access control | A buyer can only ever see and act on their own orders/tickets — order and ticket IDs are useless to any other buyer, even guessed or copied from a URL |
| Resend channels | Email and WhatsApp are both offered per ticket; see "Known limitations" below for WhatsApp's current status |
Known limitations (as of this writing)
- WhatsApp resend doesn't actually deliver yet. The button exists and the request succeeds, but no real WhatsApp provider is wired in on the backend — see the Ticket Delivery (QR + Email + WhatsApp) gap tracked in 13 — Feature Gaps Tracker. Email resend is fully live today.
- No buyer-initiated "request cancellation" action. The order/ticket detail page doesn't have a self-service cancellation entry point — cancellation is organizer-initiated only, from the organizer's own dashboard (see Organizer-Initiated Ticket/Order Cancellation below). See 13 — Feature Gaps Tracker for status.
- Only "Upcoming only" vs. "All orders" is offered — there's no dedicated "past only" filter, so seeing just past orders means choosing "All orders" and scanning past the upcoming ones.
Where this shows up
- Buyer: account menu → My Tickets → the order list (
/tickets) and each order's detail page (/tickets/[orderId]), including QR display, PDF/ZIP download, and per-ticket resend.
Organizer-Initiated Ticket/Order Cancellation
A short help-doc explaining how an organizer cancels specific tickets within an order — e.g. a fraudulent order, or a buyer request handled manually by the organizer — without cancelling the whole event.
What is this?
Organizer-initiated cancellation lets an organizer void one or more tickets within a single order, in one action, instead of cancelling the entire event. It's ticket-level, not all-or-nothing: cancelling 2 of 5 tickets in an order is a first-class action, not five individual clicks.
Refund is not automatic yet. Cancelling a ticket voids it and releases its inventory, but the organizer still processes any refund manually outside the platform for now — the buyer's notification email says so explicitly. Automated refund is tracked separately as its own feature and isn't built yet (see Known limitations). What is available now is a refund-terms preview — an estimated amount, split into ticket+tax vs. fee, shown before the organizer confirms, so they (and the buyer) know what's owed without the actual transfer being automated.
Organizer flow
- Open the event's Payments page and find the order (search/filter by buyer, status, date, etc.).
- Click View on the order row to expand its ticket list inline.
- Check the box next to each ticket to cancel — one, several, or all of them — and optionally type a reason. As soon as tickets are selected, an estimated refund due banner appears above the cancel button, prorated to just the selected tickets (e.g. "tickets + tax: ₹Y; fee: ₹Z — not refundable per policy") — this is informational only, nothing is charged or refunded yet.
- Click Cancel selected (N).
- If any selected ticket has already been checked in, a confirmation step names it and asks to confirm again before proceeding — checked-in tickets aren't cancelled silently.
- On success: the ticket(s) are voided (they'll no longer scan at check-in), any reserved seat or GA capacity they held is released back to inventory, and the buyer is emailed that their ticket(s) were cancelled.
The same underlying action is also reachable one ticket at a time from the Tickets & Delivery grid's per-row Cancel button.
Rules to know
| Rule | Behavior |
|---|---|
| Unit of cancellation | Per ticket, selectable within one order — cancel any subset, not just the whole order |
| Already-cancelled tickets | Can't be selected again; the checkbox disappears once a ticket reads Void |
| Checked-in tickets | Selectable, but require an explicit second confirmation naming the ticket before the cancel goes through |
| Eligible orders | Only tickets belonging to a paid order can be cancelled |
| Inventory release | A reserved seat returns to available immediately; a General Admission ticket's slot is excluded from the schedule's sold count as soon as it's voided, so it's sellable again right away |
| Refund | Not automatic. The organizer processes it separately; a prorated estimate is shown before confirming (ticket+tax vs. fee, honoring the order's snapshotted fee-refund terms), but confirming cancellation never creates a payment-gateway refund itself |
| Buyer notification | A real email naming the cancelled ticket(s) and the organizer's reason (if given) is sent immediately, and now states the same estimated refund figure instead of only "processed separately" |
| Audit trail | Every cancellation is recorded as an audit log entry against the ticket |
Known limitations (as of this writing)
- No automated refund. Refund automation is a separate, not-yet-built feature — see 13 — Feature Gaps Tracker for status. A refund-amount preview now exists (see Organizer flow, above), but it's read-only — nothing about it triggers an actual gateway refund or changes the order/payment status.
- No order-level "partially cancelled" indicator. If only some tickets in an order are cancelled, the order's own status stays "Confirmed" — only the individual ticket cards show the change. A buyer has to open the order and look at each ticket to see what was cancelled.
- WhatsApp isn't part of the cancellation notification — only email is sent today, consistent with the platform's current WhatsApp limitations (see Ticket Delivery, above).
Where this shows up
- Organizer: event → Payments → expand an order (View) → select ticket(s) → Cancel selected (
/organizer/events/[id]/payments); or per-ticket from Tickets & Delivery (/organizer/events/[id]/tickets). - Buyer: the cancelled ticket's card shows a red Cancelled badge with an explanation, on the order detail page in My Tickets (
/tickets/[orderId]).
Fee Transparency at Checkout
A short help-doc explaining Fee Transparency at Checkout — not a screen of its own, but a principle that Platform Fee Display, GST / Tax Engine, and cancellation/refund all have to follow consistently: every charge is itemized before payment, the refund policy for each is stated upfront, and no charge appears later that wasn't shown at checkout.
What is this?
Three guarantees, enforced end to end rather than on a single page:
- Itemized before paying — the Review & Pay order summary always shows Subtotal, Tax, Platform fee, GST on the fee, and Discount as separate lines, never folded into a single number.
- Refund terms stated upfront, for the whole order — a single refund policy summary covers the ticket price/tax and the fee/its GST together, not just the fee in isolation, and it's shown again on the order detail page after purchase, not only at checkout.
- Nothing appears later that wasn't shown here — the amount actually charged always comes from the same persisted order total the summary displays; nothing added post-checkout can diverge from it.
Buyer flow
- On Review & Pay, below the Total, a refund policy paragraph explains: ticket price and its GST are refunded in full if the organizer or platform cancels the event; the fee (and the GST on it) follow the admin-configured fee policy set by the organizer's platform-fee terms.
- After paying, opening the order from My Tickets shows the identical breakdown and refund summary — the numbers don't disappear once the order is paid.
- If an organizer later cancels some of the buyer's tickets, the cancellation email states an estimated refund amount rather than only "processed separately."
How it's enforced
- Single calculation, not duplicated logic for refunds. A shared helper (
calculateRefundAmount) computes what's refundable from an order's snapshot — used by both full-event cancellation and the organizer's per-order refund preview — so the same rule (exclude the fee and its GST when the fee isn't refundable) applies everywhere refunds are estimated, not just in one place. - Refund summary composed fresh from the snapshot, not re-stored. The whole-order refund sentence is generated from the order's already-snapshotted
feeRefundable/refund-note fields every time it's shown — at checkout and again on the order page — so it can never drift from what those two fields actually say, and a later admin settings change can't retroactively alter what a past buyer sees. - The charge is never re-derived after the summary is shown. The order's total is computed once, at order creation, and persisted — the payment step reads that same stored number rather than recalculating it, so what's charged always matches what the summary displayed.
Rules to know
| Rule | Behavior |
|---|---|
| Scope | Applies wherever a charge or a refund is shown to a buyer or organizer — checkout, the post-purchase order page, and cancellation flows |
| Refund summary composition | Ticket price + its GST: always described as refunded in full on an organizer/platform cancellation. Fee + its GST: follows the admin-configured fee refund policy, generated fresh from the order's own snapshot |
| Fee-tax exclusion | Whenever the fee itself is excluded from a refund, the GST charged on that fee is excluded too — never refunds a tax on a charge that wasn't refunded |
| Cancellation refund preview | Available for organizer-initiated per-order cancellation (see Organizer-Initiated Ticket/Order Cancellation above) — an estimate only, prorated to the tickets being cancelled; it does not execute a refund |
| Charge integrity | The amount actually charged at payment always comes from the same order total already shown in the summary — never recalculated at charge time |
Known limitations (as of this writing)
- No automated refund execution anywhere yet. Every refund "amount" described above — whether for full-event cancellation or the per-order preview — either executes against the platform's own gateway account (full-event cancellation) or is preview-only (organizer per-order cancellation, see Organizer-Initiated Ticket/Order Cancellation above). Automated refund triggered directly from a buyer- or organizer-initiated per-order cancellation is a separate, not-yet-built feature.
- The checkout review screen still computes its live preview client-side, mirroring the backend's fee/tax formulas rather than asking the server for a computed number — the two are kept in sync by shared naming/tests, but it's still two implementations of the same math. The actual charge is unaffected: it always comes from the server's response when payment is initiated, not from this client-side preview.
- The organizer's Payments page doesn't yet show the platform's own fee-tax (
platformFeeTax) as its own column — see Platform Fee Display and GST / Tax Engine above. The page's own Net Payable figure does correctly subtract the platform fee; it's the separate Sales Dashboard's older "Net revenue" figure that still doesn't — see Payment & Settlement View and Sales Dashboard below.
Where this shows up
- Buyer: the Review & Pay order summary and refund policy paragraph on the booking page (
/events/[id]/book); the same breakdown and refund summary on the order detail page (/tickets/[orderId]); the estimated-refund line in a cancellation email, when an organizer cancels part of an order. - Organizer: the estimated refund banner shown before confirming a per-order cancellation on the Payments page (
/organizer/events/[id]/payments).
QR-Based Check-in Scanning
A short help-doc explaining the gate-staff scanner: how a ticket is validated at the door, the manual fallback, and how offline scanning actually behaves.
What is this?
The Check-in scanner is what venue staff use at the entrance to validate and admit attendees — scanning each ticket's QR code, or falling back to a manual search when a code won't scan or there's no connectivity.
Staff flow
- Open Check-in for the event — organizers reach it from the Manage sidebar; dedicated gate staff (a separate role, invited per event from the scanner's own Gate staff panel) sign in and land on their own Check-in list of events they've been given access to.
- Point the camera at a ticket's QR code. The scanner checks: the code is genuine (not forged), the ticket exists for this event, it isn't voided or cancelled, its schedule is currently in its entry window, and it hasn't already been used.
- A full-screen flash confirms the result — green for a valid entry, amber for already used, red for invalid — with a matching sound/vibration, plus the ticket's serial and seat/group info.
- If a code won't scan: search the Remaining list by ticket number, seat, or attendee name, and tap Check in directly — no camera needed.
- Toggle to Manual token entry to paste a scanned value by hand as a last resort.
Rules to know
| Rule | Behavior |
|---|---|
| Access | Organizers, super admins, and invited gate staff only — staff are invited per event from the scanner's own "Gate staff" panel |
| Validation checks | Signature/forgery check, ticket exists for this event, not voided, schedule not cancelled, within the schedule's entry window, not already used |
| Re-entry | Allowed only if the organizer has enabled re-entry for the event; otherwise a second scan of the same ticket shows "Already checked in at [time]" |
| Group tickets | A single ticket with a group size greater than 1 can be scanned that many times before it's marked fully used, showing "Entry X/Y accepted" each time |
| Manual check-in | Runs the same validation rules as a camera scan — just found by search instead of by code |
| Data sharing | The same ticket/scan records power this scanner, the Live Attendance Dashboard, and the Attendee List — a check-in here shows up on both within their normal refresh interval |
Known limitations (as of this writing)
- Offline scanning shows a false "valid" before it's actually confirmed. When the device has no connectivity, a scan is queued locally and immediately flashed green as valid — without checking whether the ticket is real, already used, voided, or for the wrong schedule. The true result is only known once the device reconnects and the queue syncs, which can be well after the attendee has already walked in.
- No true conflict resolution for two devices scanning the same ticket offline. Whichever device's sync request reaches the server first is recorded as the valid entry; the second is simply told the ticket was already used — the system doesn't compare when each scan actually happened at the gate, only when it happened to sync.
- The manual Check in button (from the Remaining list) requires connectivity — unlike a camera/pasted-token scan, it isn't queued for offline use.
- The Check-in scanner's own live counters and the organizer's separate Live Attendance Dashboard (below) can show slightly different totals when tickets have been voided, since they're computed by two different queries against the same data.
- True conference-style multiple sessions (scanning separately into "Keynote" vs. "Workshop A") aren't built yet — see the note in "Live Attendance Dashboard" below.
Where this shows up
- Organizer: Check-in in the Manage sidebar (
/organizer/events/[id]/scan), including the Gate staff invite panel. - Gate staff: their own Check-in landing page listing events they've been granted access to, then the same scanner (
/staff/events/[id]/scan).
Live Attendance Dashboard
A short help-doc explaining the organizer-facing live check-in dashboard: sold vs. checked-in vs. remaining, broken down by ticket type and schedule, with a drill-down into who's actually behind each number.
What is this?
The Live Attendance dashboard is a per-event, organizer-facing view of check-in progress while an event is happening — how many tickets are sold, how many have been scanned in at the gate, and how many are still expected. It's a read-only summary view; the actual scanning happens on the separate Check-in scanner page (see the gate-staff scan console) — this dashboard is where the organizer watches the numbers roll in without holding a scanner themselves.
Organizer flow
- Open the event and go to Live Attendance (Operations section of the event's Manage sidebar).
- Four counters update automatically every few seconds: Total sold, Checked in, Remaining, and Last hr (scans in the last 60 minutes) — alongside an entry-progress bar.
- Narrow the counters with the Ticket type and Schedule dropdowns — each shows its own sold count inline (e.g. "VIP (120)"), and selecting one re-derives all four counters and the progress bar for just that slice.
- Click any counter (Total sold / Checked in / Remaining / Last hr) to drill down into the actual attendee list behind that number — respecting whichever ticket-type/schedule filter is active. A search box narrows further by ticket serial, seat, or holder name. Click the counter again, or Close, to collapse the list.
- A Synced HH:MM:SS badge next to the page title shows when the numbers were last refreshed — it turns amber and reads Stale if a poll is overdue, so a connectivity hiccup never quietly shows an out-of-date count as if it were current.
Rules to know
| Rule | Behavior |
|---|---|
| Refresh | Counters poll automatically every 5 seconds; the drill-down attendee list refreshes every 30 seconds once opened |
| "Sold" / "Remaining" | Counts valid and checked-in tickets only — a cancelled/voided ticket (see Organizer-Initiated Ticket/Order Cancellation above) is never counted as sold or as still expected to arrive |
| Ticket-type filter | Maps to the event's ticket types (price categories) — see Ticket Types & Tiered Pricing above |
| Schedule filter | Maps to the event's schedules (EventSlot) — see Schedules & Ticket Shapes above; there's no separate conference-style "session" concept yet, so schedule is the closest filter to it |
| Combining both filters | If a ticket type and a schedule are both selected, the dashboard shows the narrower of the two counts rather than a true cross-tabulation — an exact type-and-schedule combined count isn't computed today |
| Offline scans | Once a gate device syncs its queued offline scans (see the Check-in scanner's offline queue), they land in the same data this dashboard reads — no separate reconciliation step is needed, they just show up on the next poll |
| Freshness indicator | Reflects how current the dashboard's own poll is (green "Synced HH:MM:SS", amber "Stale" after ~15s without a successful refresh) — it does not track whether a specific gate device still has scans sitting unsynced in its local queue |
| Drill-down data | Reuses the same attendee list as the Check-in scanner's own "Remaining/Entered" tabs, so the two views never disagree about who's checked in |
Known limitations (as of this writing)
- No exact combined ticket-type and schedule count — selecting both filters shows the narrower of the two, not a true intersection.
- No per-device "hasn't synced in N minutes" warning — the freshness badge only reflects the dashboard's own polling, not individual gate devices' offline queues.
- Filtering by "session" really means filtering by schedule (date/time) — true conference-style sub-sessions (e.g. scanning separately into "Keynote" vs. "Workshop A" on the same ticket) aren't a built feature yet.
- The drill-down list re-fetches the full attendee roster client-side and filters in the browser rather than a server-side paginated/filtered query — fine at typical event sizes, but not built to scale to very large attendee lists.
Where this shows up
- Organizer: Live Attendance in the event's Manage sidebar (
/organizer/events/[id]/attendance) — counters, filters, freshness badge, and the click-to-drill-down attendee list.
Attendee List & Export
A short help-doc explaining the organizer's Attendees page: the full roster for one event — name, ticket type, registration answers, and check-in status in a single searchable, filterable, exportable list.
What is this?
Attendees is a per-event, organizer-facing list of everyone holding a valid or checked-in ticket — the roster view, as opposed to Tickets & Delivery (delivery/resend/cancel actions) or Live Attendance (real-time check-in counters). It answers "who's actually coming" and "who's actually here" from one screen, and lets the organizer export exactly what they're looking at as a CSV.
Organizer flow
- Open the event and go to Attendees (Operations section of the event's Manage sidebar, next to Tickets & Delivery).
- The list shows every non-cancelled ticket: serial, holder name (with email/phone underneath), ticket type, seat, and check-in status — checked-in rows also show the check-in time and gate.
- Narrow the list with the search box (name, email, phone, or serial), the ticket type dropdown (populated from the event's configured ticket types), and the checked-in / not checked-in dropdown. All three combine.
- If the event has any custom registration fields configured (see Custom Registration Form Builder above), a Show registration answers toggle appears — switching it on adds each attendee's answers inline under their name.
- Click Export CSV to download exactly the rows currently on screen — respecting whatever search/filters are active. Leave every filter at its default to export the full roster instead.
Rules to know
| Rule | Behavior |
|---|---|
| Who's included | Every ticket except voided/cancelled ones — a cancelled ticket isn't someone anyone still expects to check in |
| Data source | The same per-event ticket data that powers Tickets & Delivery, so the two pages never disagree about a holder's name, email, or custom-field answers |
| Ticket type | Read from the order's price category at the time it was booked; complimentary tickets minted via Bulk mint (no price category) always show Uncategorized and aren't matched by any specific ticket-type filter option |
| Check-in status | Derived from the ticket's status plus its latest successful gate scan — the same scan data the Check-in scanner and Live Attendance dashboard write to, so all three views agree |
| Registration answers | Only fields the organizer actually configured for this event's ticket types (Feature 7) are shown — there's nothing to toggle on for an event with no custom fields |
| Export scope | Always matches the current search/ticket-type/checked-in filters — never silently exports more or less than what's visible in the table |
| Export format | CSV, generated in the browser from the already-loaded, already-filtered rows — no separate export job or email delivery |
Known limitations (as of this writing)
- The list and its filters run client-side against the full per-event ticket roster, the same approach the Live Attendance drill-down uses — fine at typical event sizes, but not built to scale to very large attendee lists with server-side pagination.
- CSV export is client-side only; there's no backend export endpoint, scheduled export, or emailed report.
- Ticket-type filtering can't isolate complimentary/bulk-minted tickets from each other — they all collapse into the same "Uncategorized" bucket regardless of which guest list they came from.
Where this shows up
- Organizer: Attendees in the event's Manage sidebar (
/organizer/events/[id]/attendees) — search, ticket-type filter, checked-in filter, registration-answers toggle, and CSV export.
Payment & Settlement View
A short help-doc explaining where an organizer sees what's been collected, what they're actually owed, and what's already been paid out.
What is this?
Three related views, at three different scopes:
- Payments (per event) — the order-level ledger for one event: every order, its payment/refund status, filters, an Excel export, and where a per-order cancellation is actually processed (see "Organizer-Initiated Ticket/Order Cancellation" above).
- Payments & Settlements (account-wide) — an aggregate summary across every event the organizer runs, plus their payout history.
- Admin Payments — the same order-level ledger as the per-event view, but platform-wide across every organizer, with a place for a super admin to record a payout.
Organizer flow
- Open an event and go to Payments to see its order ledger: stat cards for Orders, Tickets Sold, Gross, Fees, Refunds, and Net Payable; a filterable/searchable order table; and an Analytics tab that buckets orders into groups like "Abandoned Checkouts" or "Payment Captured, Order Still Pending" so problems are easy to spot.
- Use Filters to narrow by buyer name, order/payment status, gateway, or date range, then Download all Excel to export exactly what's filtered.
- For the bigger picture, go to Payments & Settlements (top-level nav) to see the same Gross/Fees/Refunds/Net Payable numbers totalled across every event you run, plus a Payout history list and a Pending Payout figure.
- If any GST TCS (Sec. 52) or income-tax TDS (Sec. 194-O) has been withheld from your payouts, a Marketplace withholding to date panel shows the running totals just above the payout history, and each payout history entry that had withholding notes it inline.
Marketplace withholding (GST TCS / income-tax TDS)
As an e-commerce operator, the platform is required to withhold GST TCS (Sec. 52, currently 0.5%) and income-tax TDS (Sec. 194-O, currently 0.1%) from what it pays organizers, on top of recording the payout itself — see 15-GST-TCS-TDS-Taxation-Guide.md for the full explanation and a worked example. When an admin records a settlement (see the admin flow below), they can enter the TCS and TDS actually withheld alongside the net cash amount — the admin's form pre-fills both from a computed suggestion (net ticket value × the statutory rate, minus whatever's already been withheld on past settlements), editable before saving.
Neither withheld amount is lost to you: TCS lands as a credit in your GST electronic cash ledger (claim it via GSTR-3B), and TDS shows up in your Form 26AS — both usable against your own final tax liability. Pending Payout already accounts for this: TCS/TDS recorded on a settlement counts as "discharged" from what you're owed, the same as the cash portion, since neither remains owed to you as cash.
Worked example
An organizer sells 10 tickets at ₹1,000 each on a taxable event (18% GST), platform fee 2%.
- Checkout, per ticket: buyer pays ₹1,000 + ₹180 (organizer's GST) + ₹20 (platform fee) = ₹1,200. Across 10 tickets: ₹10,000 ticket value, ₹1,800 tax, ₹200 fee.
- Payments & Settlements page: Net Payable = ₹10,000 + ₹1,800 − ₹200 = ₹11,600. The withholding base (net taxable ticket value) = Net Payable − tax collected = ₹11,600 − ₹1,800 = ₹9,800.
- Admin opens Settlements for this organizer: sees
Net Payable: INR 11,600,Pending Payout: INR 11,600, and a suggestion ofTCS (0.5%) INR 49, TDS (0.1%) INR 9.80(0.5%/0.1% of the ₹9,800 base) — pre-filled into the form. - Admin records the actual bank transfer: Net cash amount
₹11,541.20(= 11,600 − 49 − 9.80, what really left the bank), TCS withheld₹49, TDS withheld₹9.80. - Pending Payout recalculates to ₹0 (₹11,600 − (11,541.20 + 49 + 9.80)) — fully settled. A partial payout would leave the remainder in Pending Payout, and the next settlement's suggestion automatically subtracts what was already withheld, so it never double-suggests.
- Organizer's own page shows a "Marketplace withholding to date" panel (
TCS withheld: ₹49 · TDS withheld: ₹9.80) and the same figures inline on that payout history row.
This never moves real money on its own — the admin still transfers the cash manually and is just recording what was withheld, the same way the rest of this page's settlement recording already worked before TCS/TDS existed.
Rules to know
| Rule | Behavior |
|---|---|
| Net Payable | Gross paid-order value, minus coupon discounts, minus the platform fee and its GST, minus refunds — this is the figure to trust for what you're actually owed |
| Pending Payout | Net Payable to date, minus whatever's already been recorded as paid out and whatever's been recorded as TCS/TDS withheld — recalculated fresh each time, not a stored running balance |
| Payout recording | Manual only — a super admin logs an amount, date, method, reference note, and optionally TCS/TDS withheld against your account; nothing here triggers a real bank transfer |
| Withholding base | TCS/TDS are suggested on the net ticket value excluding your own GST (i.e. Net Payable minus tax collected) — matching how they're actually assessed, not on the tax-inclusive total |
| Scope | Per-event Payments has full order-level detail and export; the account-wide page is summary-only, with no order rows or export |
Known limitations (as of this writing)
- No automated payout. Settling an organizer is a manual, admin-recorded bookkeeping entry — there's no integration that actually moves money to an organizer's bank/UPI account yet. The TCS/TDS withholding fields are recorded the same manual way, alongside the cash amount, not auto-deducted by any real transfer.
- The platform fee's own GST (
platformFeeTax) is calculated and stored on every order, but isn't shown as its own column in either the on-screen order table or the Excel export — you'd need to derive it from Fee and Total for now. - A separate, older "Net revenue" figure on the Sales Dashboard still has the fee-subtraction bug this Payments page has since fixed. The Sales Dashboard's "Net revenue (after refunds)" stat is Gross minus Refunds only — it does not subtract the platform fee, so it overstates what you're actually owed there. Use this Payments page's Net Payable figure as the source of truth; see "Sales Dashboard" below.
- A recorded settlement (including its TCS/TDS amounts) can't currently be edited or deleted if entered incorrectly.
- The suggested TCS/TDS amount is a running balance computed from aggregate totals, not a per-order ledger — it doesn't individually track which specific orders' withholding has been recorded, the same level of precision Net Payable itself already uses elsewhere on this page.
- No GSTR-8 filing or TCS-certificate generation, and no automatic ₹5 lakh/year 194-O exemption tracking for a resident individual/HUF organiser — both remain manual, off-product steps. See 13-Feature-Gaps-Tracker.md.
Where this shows up
- Organizer: Payments in the event's Manage sidebar (
/organizer/events/[id]/payments); Payments & Settlements in the top-level nav (/organizer/payments). - Admin: Admin → Payments (
/admin/payments), platform-wide, with a Settlements action per organizer to record a payout.
Sales Dashboard
A short help-doc explaining the organizer-facing Sales Dashboard: revenue, tickets sold vs. available, and a sales trend leading up to the event.
What is this?
The Sales Dashboard is a per-event, organizer-facing summary of sales performance — total revenue, how many tickets sold, which ticket type and which schedule sold best, and a day-by-day trend leading up to the event. It answers "how is this event selling," at a glance, without reading a transaction table.
It deliberately overlaps a little with the Payments page, but the two serve different jobs:
- Sales Dashboard — the aggregate view. Revenue and volume, broken down by ticket type/schedule, plus the trend. No per-order detail, no actions.
- Payments — the ledger. Every individual order (buyer, gateway, payment/refund status), Excel export, and where an organizer actually processes a per-order cancellation (see Organizer-Initiated Ticket/Order Cancellation above).
Both read from the same underlying paid-order data, so the headline revenue figures agree between them — see "Rules to know" below for one important place they diverge (the ticket-type/schedule breakdown, unlike the headline stat, excludes the platform fee).
Organizer flow
- Open the event and go to Sales Dashboard (Operations section of the event's Manage sidebar, between Live Attendance and Notifications).
- Five stat cards summarize the event: Gross revenue, Net revenue (after refunds), Refunded, Tickets sold, and Orders.
- The sales trend bar chart shows revenue per day, from the first paid order through the event's earliest schedule date — hover a bar for that day's exact revenue and order count.
- Revenue by ticket type lists every ticket type sold, with sold count, remaining availability (when the type has a quantity cap), and revenue — sorted highest revenue first.
- Revenue by schedule shows the same breakdown by date/session instead of ticket type, useful once an event has more than one schedule.
- Hovering the small info icon next to Sales Dashboard (and next to Payments) in the sidebar shows a one-line reminder of what each page is for, so it's easy to tell them apart at a glance.
Rules to know
| Rule | Behavior |
|---|---|
| Confirmed sales only | Every figure counts paid orders only — a pending or failed order contributes nothing to any total on this page |
| Gross / Net revenue | Sum of paid orders' total (ticket price + tax + platform fee + GST on the fee − discount) — the same order-total definition the Payments page and the main dashboard's per-event summary card already use. Net revenue is Gross minus the Refunded stat |
| Refund exclusion | Refunded amount is shown as its own stat, never folded silently into Gross revenue |
| Revenue by ticket type / schedule | Ticket price plus its own tax only, summed per ticket — this excludes the platform fee, its GST, and any order-level coupon discount, since none of those are allocated to a specific ticket line. Because of that, the ticket-type/schedule figures won't sum exactly to the Gross revenue stat card above them — the gap is the fee, fee-GST, and any discount |
| Refunds not reflected in the breakdowns | Only the headline Net revenue stat subtracts refunds; the ticket-type and schedule breakdowns are not refund-adjusted |
| Per-ticket cancellation exclusion | A ticket voided via Organizer-Initiated Ticket/Order Cancellation (see above) is excluded from ticket-type/schedule sold counts and revenue even though its parent order still reads Paid — checked from the ticket's own status, not the order's |
| "Available" | Shown only when a ticket type or schedule has a capacity set (quantity cap / schedule capacity); an unlimited one shows just a sold count, with no available figure or occupancy percent |
| Refresh | Loaded once per visit (cached ~30 seconds) — not live-polled every few seconds the way Live Attendance is, since this is a performance summary rather than a real-time gate feed |
| Trend range | Daily buckets from the first paid order's date through the event's earliest schedule date (or today, if that date hasn't arrived yet) |
Known limitations (as of this writing)
- "Net revenue" on this page is Gross minus the Refunded stat only — it does not subtract the platform fee. This overstates what the organizer actually gets to keep. The per-event Payments page has its own, corrected Net Payable figure (Gross − fees − refunds) — see "Payment & Settlement View" above; trust that one over this page's "Net revenue" for payout purposes.
- Ticket-type and schedule revenue exclude the platform fee, its GST, and coupon discounts (see "Rules to know" above) — for the fully reconciled per-order total including those, use the Payments page.
- No date-range picker for the trend chart — it always spans from the first paid order to the event's earliest schedule date.
- No CSV/Excel export from this page — export transaction-level detail from Payments, or the roster from Attendees, instead.
- No live polling — refresh the page to pick up orders placed in the last few seconds, unlike Live Attendance's automatic 5-second refresh.
- No trend chart when the event has had no paid orders yet — the panel just says "No sales yet."
Where this shows up
- Organizer: Sales Dashboard in the event's Manage sidebar (
/organizer/events/[id]/sales), between Live Attendance and Notifications — stat cards, the sales-trend chart, and the ticket-type/schedule breakdowns. A hover tooltip on the sidebar entry (and on Payments) explains what each page is for.
Event Detail Page
A short help-doc explaining the public Event Detail Page: what a buyer sees, and how the page is built so it can actually be found and shared.
What is the Event Detail Page?
The Event Detail Page (/events/[id-or-slug]) is the full public page for a single event — everything a buyer needs to decide whether to book: banner/gallery, description, venue with an embedded map, a schedule picker when the event has more than one date (or an event-wide date range and starting price when it runs at more than one venue), and ticket types with pricing. It's the page every listing card, search result, and shared link points to.
Buyer flow
- Buyer arrives from the events list, a search result, a direct link, or a link shared by someone else (WhatsApp, social, email).
- The page shows the event's banner/gallery, category and tags, highlight badges, artists, description, "You should know" notes, reviews, and FAQs/terms.
- If the event has more than one schedule at a single venue, a date picker lets the buyer switch between them — the venue, time, and prices shown update to match the schedule selected. If the event runs at more than one venue, the page instead shows the event-wide date range and an "onwards" starting price (pulled across every venue's schedules), and picking a specific venue/date is deferred to the booking page's Venue step (see "Schedules & Ticket Shapes" above).
- The sidebar shows the resolved date/time, duration, age limit, languages, venue (with a map and a link to Google Maps), and ticket pricing (from-price, or an itemized list when prices vary).
- Book Now carries the selected schedule (and any promo/unlock-code from the URL) into the booking flow — for an event with more than one venue, no schedule is pre-selected, so Book Now opens straight to the booking page's Venue step instead (see the Schedules & Ticket Shapes and Reserved Seating / General Admission guides above for what happens next).
- A Share button uses the device's native share sheet where available, or copies the page's link to the clipboard otherwise.
How the page is built for sharing & search
- Server-rendered first paint — the event's own content (title, description, price, dates) is present in the page's initial HTML, not loaded afterward in the browser. This is what lets search engines and the non-JS link-preview scrapers used by WhatsApp, Slack, iMessage, etc. see the real event instead of a blank shell.
- Per-event metadata — the page
<title>, meta description, Open Graph tags (what a shared link's preview card is built from), Twitter Card, and a canonical URL (the event's slug when it has one, its raw ID otherwise) are all generated per event — not the one static site-wide title every event page used to share. - Structured data (JSON-LD) — a
schema.org/Eventblock is embedded on every event page (one entry per schedule, for multi-date events), so search engines that support it can show rich results — dates, venue, price — directly in a search listing. - Sitemap & robots — every published event's canonical URL is listed in
/sitemap.xml, refreshed periodically./robots.txtallows crawling of public pages and blocks private/dashboard routes (organizer, admin, staff, a buyer's own saved/ticket pages) and the booking flow itself. - Preview links (
?preview=1, used by an organizer reviewing an unpublished event from the Manage page) are markednoindexand never enter the sitemap — they require the organizer's own login to load anyway, so a crawler could never reach one regardless.
Draft, cancelled & ended events
- Draft, unpublished, or a nonexistent ID — the URL shows a normal "Page not found" page rather than a raw error message.
- Cancelled — the page still loads, so anyone who already has the link (e.g. someone who was holding a ticket) can see it, but a cancellation banner replaces the schedule picker and pricing, and Book Now is disabled. The event's structured data also switches to
EventCancelled. - No upcoming schedules (every date has passed, but the event is still published) — the page shows "This event has no upcoming schedules" instead of an empty date picker, and booking is disabled the same way.
- In every case, the checkout API independently refuses to create an order for a non-published event — disabling the button on the page is a courtesy for the buyer, not the only thing stopping a booking.
Rules to know
| Rule | Behavior |
|---|---|
| Access | Public — no login required; works from a search result, a shared link, or the raw URL |
| Indexability | Server-rendered with per-event metadata and JSON-LD; every published event is listed in /sitemap.xml |
| Preview mode | ?preview=1 requires the organizer's own login, is noindex, and is never included in the sitemap |
| Draft or nonexistent event | Styled "Page not found" page |
| Cancelled event | Still viewable read-only — booking is disabled on the page and rejected by the checkout API either way |
| Event past its last schedule | Still viewable — booking disabled, "No upcoming schedules" shown in place of the date picker |
| Canonical URL | The event's slug when it has one, otherwise its raw ID |
Known limitations (as of this writing)
- The events listing page (
/events) doesn't yet have the same server-rendering/metadata treatment as the detail page — it's still client-rendered, so search engines see it the way the detail page used to look before this update.
Where this shows up
- Buyer: any
/events/[id-or-slug]URL — reached from the events list, search, a shared link, or a marketing/QR link. - Organizer: the same page in preview mode (
?preview=1), reachable from the event's Manage page while it's still a draft.
Event Listing & Search
A short help-doc explaining how the buyer-facing browse/search page finds events, and which controls are server-driven vs. local refinements.
What is this?
The events browse page (/events) is where a buyer searches or filters down to the event they want, and the homepage's discovery rails (Happening soon, This weekend, Filling fast, By category) offer the same event pool pre-curated into shorter lists. Every result card links to the Event Detail Page above.
Buyer flow
- Buyer opens
/eventsdirectly, follows a homepage rail, or submits a keyword from the homepage's hero search box. - The keyword box, event-type tabs, and category chips each trigger a fresh, server-side search — the full result set behind a filter is always considered, not just whatever happened to already be on screen. The category chip row itself — which chips appear, their order, and the live count on each — is also server-driven (
GET /events/categories, scoped to city and type), so it stays stable across pages and sorts instead of shifting based on whatever 24 events happen to be loaded. - Opening Filters reveals a city dropdown, date-range buttons (Today / This weekend / Next 7 days), a max-price slider, and — once a type/category is picked — a contextual tag-chip row.
- Sort defaults to Popularity; switching to Date or Price re-orders whatever's currently loaded.
- Results page in batches of 24, with Previous/Next controls once there are more matches than fit on one page.
- Tapping a card opens the Event Detail Page (see above).
What search matches, and what's server-side vs. client-side
- Keyword search matches event title, description, category, venue name, city, and organizer's business name — all case-insensitive substring matches, run entirely on the server (
GET /events?q=...). Typing a term always searches the whole catalog, not just the currently loaded page. - Type tabs, category chips, and the city dropdown are also server-side filters (
type,category,cityquery params) — picking one issues a new, correctly-scoped fetch rather than re-filtering whatever was already on screen. - The category chip set itself (not just filtering by it) comes from
GET /events/categories?city=&type=— it returns only categories with live inventory for the active city/type, ordered by the admin's taxonomy order (see Event Categories & Tagging above) with admin-hidden categories excluded, each with a live count. This is what makes the chip row on both the homepage and/eventsreflect real, city-scoped inventory instead of whatever page of results happened to load. - City selection is remembered across the homepage and
/events— picking a city on either page (vialocalStorage, no login required) carries it to the other, so the category chips and results both stay scoped to the same city without re-picking it. - Sort → Popularity is server-side too: events are ordered by how many tickets have ever been issued for them (
ORDER BYa ticket-count aggregate), most first. Sort → Date/Price stay client-side, re-ordering only the events on the current page. - Date range, max price, and tag chips are client-side refinements applied on top of whichever page of server results just loaded — they narrow what's visible on that page rather than re-querying the server. For a very large result set, combine them with a keyword/category/city search first to get a smaller, more relevant page to refine.
- Pagination (
page/pageSize, 24 per page) is real — Previous/Next moves through the actual full result set, not just a client-side slice of one batch.
Rules to know
| Rule | Behavior |
|---|---|
| Search fields | Title, description, category, venue name, city, organizer's business name — case-insensitive, substring match |
| Type/category/city filters | Server-side; each change re-queries the API |
| Popularity sort | Server-side; ranked by all-time tickets issued for the event |
| Date/price sort | Client-side; only reorders the currently loaded page |
| Date/price/tag refinement | Client-side; only narrows the currently loaded page |
| Page size | 24 events per page on /events; homepage rails and the saved-events page pull up to 100 to have enough events to curate from |
| City list | Drawn live from the distinct cities among published, public events — not a fixed/managed list |
| Category chip source | GET /events/categories?city=&type= — live counts, admin-configured order, admin-hidden categories excluded |
| City persistence | Remembered in the browser (localStorage) and shared between the homepage and /events; no login required |
Known limitations (as of this writing)
- Date range, max price, and tag-chip filtering don't reach past the currently loaded page — a very large, popular category could need a keyword/category/city search to narrow results before those refinements find everything relevant.
- Date and Price sort only reorder the current page; only Popularity is a true whole-catalog sort.
- The listing page itself isn't server-rendered/indexable the way the Event Detail Page now is (see that section's Known Limitations) — it's still a client component fetching data via
useQuery.
Where this shows up
- Buyer:
/events(search box, type tabs, category chips with live counts, Filters panel, Sort dropdown, pagination), and the homepage's hero search box, city display/picker, and discovery rails (including the "By category" strip).