Skip to main contentSkip to main content
Equaticket
Getting Started

Trying the Interactive Demo

Last updated April 13, 202618 min read
In this article

Trying the Interactive Demo

18 min read · Getting Started

Feature Spec: Interactive Demo Event Flow

Status: Draft

Author: Drew Connerton

Date: 2026-04-13

Feature tag: MKT-01

---

Problem Statement

Prospective organizers visiting the Equaticket marketing site have no way to experience the buyer flow before signing up. They can read about the platform, view screenshots, and compare pricing — but they cannot touch a live event page or go through checkout themselves. This creates doubt at the most critical moment in the conversion funnel: the decision to sign up. Competitors like Luma and Eventbrite benefit from their large existing catalog of real public events that serve as implicit demos. Equaticket has no such catalog yet, and needs a purpose-built demo path to close that gap.

---

Goals

  1. A prospective organizer can go from the marketing site to a completed test ticket purchase (including receiving a real ticket delivery email) in under 3 minutes, without creating an account.
  2. The demo event page is visually indistinguishable from a real event page — it uses the same components, same layout, same checkout flow — so the experience is a genuine preview, not a mockup.
  3. Demo activity is fully isolated from production data: it never counts toward billing metrics, ticket caps, MRR snapshots, or any aggregate query.
  4. The demo checkout endpoint cannot be abused as a vector for spam email delivery or load on Stripe's test API.
  5. A CTA linking to the demo is placed on the homepage, pricing page, and all comparison pages (Eventbrite, Luma, Tixr alternatives).

---

Non-Goals

  • Multiple demo events. One canonical demo event is sufficient for v1. Supporting organizer-created "sandbox" events is a separate, larger feature.
  • Demo for the organizer dashboard. This spec covers the buyer experience only. An interactive dashboard demo (guided tour, sandbox org) is a separate feature.
  • Anonymous checkout without email. The demo requires a real email address so the ticket delivery email can be sent — that experience is the payoff.
  • Persistent demo order records. Demo orders can be cleaned up by cron after 7 days. They do not need to be retained.
  • Custom demo event per vertical. A single generic demo event (e.g. "The Equaticket Demo Show") covers all verticals at launch. Vertical-specific demos (comedy club, festival, workshop) are a future enhancement.
  • Stripe Connect. The demo bypasses Connect entirely and runs against the Equaticket platform test account. No connected account is involved.

---

User Stories

Prospective Organizer

  • As a prospective organizer browsing the marketing site, I want to try the ticket purchase experience myself so that I can see exactly what my buyers will see before I commit to signing up.
  • As a prospective organizer on the demo page, I want clear instructions telling me what email and card number to use so that I can complete the purchase without guessing.
  • As a prospective organizer, I want to receive a real ticket delivery email so that I understand the full end-to-end experience my buyers will have.
  • As a prospective organizer, I want the demo page to look like a real event page so that I trust the preview is accurate and not a simplified mockup.

Platform (Security / Ops)

  • As the platform operator, I want rate limits on demo checkout submissions so that a single IP or email address cannot abuse the flow to generate spam emails or hammer Stripe's test API.
  • As the platform operator, I want demo orders flagged and excluded from all billing, metrics, and cap enforcement queries so that internal dashboards remain clean.

---

Requirements

P0 — Must Have (Launch Blockers)

Demo event database record

  • Add is_demo BOOLEAN NOT NULL DEFAULT false column to the events table (migration 20260413_demo_event).
  • Add is_demo BOOLEAN NOT NULL DEFAULT false column to the orders table in the same migration.
  • Seed one demo event row in a migration or a dedicated seed script. The event should be:

- Status: published

- Title: "The Equaticket Demo Show" (or similarly generic — editable via admin)

- A plausible description, venue, and a single ticket type named "General Admission — Demo" at a realistic price (e.g. $25.00)

- starts_at: set to 14 days from the seed date; kept evergreen by the demo-event-date-refresh cron (see below)

- is_demo = true

- Accessible at a fixed slug: /events/equaticket-demothe slug must never change (it is hardcoded into marketing CTAs); treat it as immutable even though other fields are editable

  • The demo event must not appear in any event listing, search, or public API response. Gate all list queries with WHERE is_demo = false (or AND NOT is_demo). Audit the following locations and add the guard:

- GET /api/v1/events (public API)

- GET /api/v1/public/events (unauthenticated public API)

- Any internal event-list queries used by the dashboard

- MRR snapshots, ticket-cap enforcement, and audience/buyer-summary queries

Demo-aware checkout flow

  • In POST /api/checkout/session (or wherever Stripe Checkout sessions are created), detect is_demo = true on the event being purchased.
  • When is_demo = true:

- Use STRIPE_TEST_SECRET_KEY instead of the connected account's live secret key.

- Do not pass a stripe_account header — route the session to the Equaticket platform test account directly.

- Set is_demo = true on the resulting orders row at creation time.

  • Demo orders must not count toward the organizer's monthly ticket cap or any billing aggregate. Add AND NOT is_demo guards wherever orders or tickets tables are queried for cap enforcement or billing metrics.

Stripe test webhook handling

  • Stripe sends test-mode webhooks to a separately registered endpoint, or the same endpoint can detect livemode: false on the incoming event object.
  • Recommended approach: in the existing Connect webhook handler (POST /api/webhooks/stripe), add an early branch: if (event.livemode === false) { return handleDemoWebhook(event); }.
  • handleDemoWebhook should:

- Verify the test webhook signature using STRIPE_TEST_WEBHOOK_SECRET.

- On checkout.session.completed: create orders + tickets rows with is_demo = true, fire ticket delivery email via the normal sendEmailWithOverride path (real Resend send to the buyer's real email address).

- On all other event types: no-op with a 200 response.

  • Register a Stripe test-mode webhook in the Equaticket Stripe dashboard pointing to the same /api/webhooks/stripe endpoint, listening for checkout.session.completed only.

Demo event page with organizer-facing instructions

  • The public event page (/events/[slug]) renders normally for the demo event with no structural differences — same components, same layout, same ticket purchase flow.
  • Add a dismissible banner rendered only when event.is_demo === true. The banner must:

- Be visually distinct (e.g. amber/blue "info" style, not an error state).

- Be positioned prominently — above the event title or pinned below the header — so it is seen before the user scrolls to the ticket widget.

- Contain the following information (exact copy to be finalized, but must include all of these elements):

- A one-sentence framing: "This is an interactive demo — you're seeing exactly what your buyers will see."

- Email: "Enter your real email address — you'll receive an actual ticket delivery email."

- Card number: 4242 4242 4242 4242

- Expiry: Any future date (e.g. 12/30)

- CVC: Any 3 digits (e.g. 123)

- ZIP: Any 5-digit ZIP (e.g. 10001)

- A closing note: "No real charge will be made."

- Be collapsible/dismissible with localStorage persistence (so repeat visitors don't see it on every load after dismissing).

  • The banner must not appear on any non-demo event page. Gate strictly on is_demo.

Rate limiting on demo checkout

  • Apply rate limiting at the /api/checkout/session route specifically for demo event requests.
  • Limits (enforced via Upstash Redis, consistent with existing rate-limit patterns):

- Per IP: Maximum 3 demo checkout sessions initiated per 60-minute rolling window.

- Per email address: Maximum 3 demo checkout sessions initiated per 24-hour rolling window.

- Global: Maximum 50 demo checkout sessions initiated per hour across all IPs (circuit breaker to prevent coordinated abuse).

  • On limit exceeded: return HTTP 429 with a user-friendly error message in the checkout UI ("You've reached the demo limit — sign up to create your own events and explore the full platform.").
  • Redis key patterns:

- demo:ip:{hashedIP} with 60-minute TTL

- demo:email:{hashedEmail} with 24-hour TTL

- demo:global with 60-minute TTL (counter, not TTL-based — use a sliding window or reset at top of hour)

  • IP should be extracted from x-forwarded-for (Vercel standard). Hash IPs before storing (SHA-256, no salt needed — this is rate limiting, not identity).
  • Email should be extracted from the checkout session creation request body. Hash before storing.

Cloudflare Turnstile on demo checkout

  • The demo checkout initiation (the point where the buyer clicks "Get Tickets" or equivalent) must require a valid Turnstile token, consistent with the existing checkout flow.
  • If Turnstile is already applied to all checkout flows, no additional work is needed here — just confirm the demo path is not exempt.
  • If the demo uses a separate initiation endpoint, explicitly add Turnstile validation.

Marketing site CTAs

  • Add a "Try a live demo →" (or equivalent) CTA to the following pages. The CTA should link to /events/equaticket-demo (the demo event page):

- Homepage (/): In the hero section, as a secondary CTA alongside the primary "Get started free" button.

- Pricing page (/pricing): Below or adjacent to the plan comparison table — e.g. "Not sure yet? Try a live demo first →"

- Eventbrite comparison page

- Luma comparison page

- Tixr comparison page

  • The CTA label should make clear this is an interactive buyer experience, not a video or screenshot tour.

Demo event date refresh cron

  • Add a new cron job demo-event-date-refresh running daily at 02:00 UTC.
  • Purpose: keep the demo event looking like a live upcoming event at all times, without ever expiring or requiring manual intervention.
  • Logic:

- Fetch the demo event (by is_demo = true).

- If starts_at is less than 21 days in the future, update starts_at to NOW() + INTERVAL '30 days' and ends_at to NOW() + INTERVAL '30 days' + [original event duration].

- The threshold (21 days) and target horizon (30 days) are constants — adjust freely without code changes by making them env vars if desired.

- Do not alter any other field on the event row.

- Log the result (updated / skipped) to Sentry or console.

  • This cron is what resolves Open Question 3 — the date is always realistic and near-future, never a year 2099 placeholder. Prospective organizers see a real-looking event date.

Demo order cleanup cron

  • Add a new cron job demo-order-cleanup running daily at 03:00 UTC.
  • Deletes orders, tickets, and pending_checkout_attendees rows where orders.is_demo = true AND orders.created_at < NOW() - INTERVAL '7 days'.
  • Log count of deleted rows to Sentry or console for observability.
  • This cron does not delete the demo event row itself — only the orders generated from it.

---

P1 — Nice to Have

Post-purchase CTA on checkout success page

  • After a demo purchase completes, the checkout success page (/checkout/success) should render an additional CTA block (only when order.is_demo = true):

- Headline: "Ready to sell tickets like this?"

- Body: "You just experienced exactly what your buyers see. Create your free account and have your first event live in minutes."

- Button: "Create your free account →" linking to /signup

  • This converts the demo's highest-intent moment (just completed a purchase) directly into a signup.

UTM tagging on demo CTA links

  • All marketing-site CTAs linking to the demo event page should include UTM parameters: ?utm_source=marketing&utm_medium=cta&utm_campaign=demo&utm_content={page_name} so demo-driven signups are attributable in analytics.

Demo order excluded from admin support queue

  • support_requests that reference a demo order (via context.order_id on an order with is_demo = true) should be filterable in the admin support console. Add an "is demo" badge in the admin support request detail view for these cases.

Disable AI triage for demo-sourced support tickets

  • The ai-triage-tickets cron should skip support_requests where the referenced order is a demo order, to avoid wasting AI API budget on demo noise.

---

P2 — Future Considerations

  • Multiple demo events by vertical — separate demo events for comedy clubs, festivals, and workshops so the experience resonates more with organizers from each segment.
  • Organizer sandbox — a full authenticated sandbox environment where a signed-up organizer can create test events and run test purchases, isolated from their live account.
  • Demo analytics — track demo completion rate (initiated checkout → received ticket email), time-to-complete, and demo-to-signup conversion rate as a dashboard widget in the admin metrics page.
  • Guided walkthrough overlay — an optional step-by-step tooltip overlay on the demo event page that explains each section ("This is your event header — you control the name, date, and cover image") for organizers who want a narrated tour alongside the interactive experience.

---

Security Considerations

Stripe test key exposure

  • STRIPE_TEST_SECRET_KEY and STRIPE_TEST_WEBHOOK_SECRET are server-side only — never exposed to the client. Standard Next.js env var discipline applies (no NEXT_PUBLIC_ prefix).
  • The test publishable key (STRIPE_TEST_PUBLISHABLE_KEY) must be set as a NEXT_PUBLIC_ var and is intentionally public — this is safe and expected by Stripe.
  • When constructing the Stripe Checkout session for demo events, the client-side must use NEXT_PUBLIC_STRIPE_TEST_PUBLISHABLE_KEY instead of NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY. This swap happens server-side: the session creation response includes which publishable key the client should use to redirect to Stripe Checkout.

Email abuse vector

  • The demo flow sends a real Resend email to whatever address the buyer enters. Without rate limiting, this is a direct spam relay.
  • Per-email rate limiting (3 per 24 hours) is a P0 requirement, not optional.
  • Turnstile is the first line of defense; IP + email rate limiting is the second.
  • Consider adding a note in the Turnstile failure path that logs suspiciously high failure rates from a single IP to Sentry.

Demo event editability via admin console

The demo event must be editable without a deploy — Drew needs to be able to update the event title, description, cover image, banner image, venue, ticket type name, price, and other content fields to keep the demo looking polished over time.

  • Add a Demo Event management page in the admin console at /admin/demo-event.
  • This page renders the standard event editor form (reuse event-form.tsx or an equivalent admin-wrapped version) pre-populated with the demo event's current field values.
  • All standard event fields are editable: title, description, cover image, banner image, venue name, venue address, lat/lng, ticket type(s), ticket price, capacity.
  • Fields that are not editable through this interface:

- slug — hardcoded into marketing CTAs, must never change; display it read-only with an explanatory note.

- starts_at / ends_at — managed exclusively by the demo-event-date-refresh cron; display current values read-only with a note: "Date is kept evergreen automatically by the demo-event-date-refresh cron."

- is_demo — not exposed; always true.

- status — always published; not exposed.

  • On save, the admin form PATCHes the demo event via the internal event update route. The route must allow is_demo = true events to be updated when the request comes from an authenticated admin session (bypass the immutability guard described below for non-admin paths).
  • The admin console nav should include a "Demo Event" link under a "Marketing" or "Platform" section heading.

Demo event protection from organizer-facing flows

  • The demo event must never appear in any organizer's event list or be editable through the standard organizer dashboard (/dashboard/events). The is_demo = true flag gates all organizer-facing event queries.
  • The demo event must never be cancellable, pausable, or archiveable through organizer-facing routes. Add a guard: if event.is_demo = true and the request is not from an admin session, return 403.
  • These protections are in addition to — not instead of — the admin editability above. The distinction is: admin session = full edit access; organizer session = no access.

Demo orders excluded from refund flow

  • POST /api/orders/[id]/refund should check order.is_demo = true and return 400 "Demo orders cannot be refunded" — there is no real charge to refund.

---

Data Model Changes

TableColumnTypeNotes
eventsis_demoBOOLEAN NOT NULL DEFAULT falseFlags the demo event; gates list queries
ordersis_demoBOOLEAN NOT NULL DEFAULT falseSet from event at order creation; gates billing/cap queries

Migration: 20260413000001_demo_event_flag.sql

Partial indexes recommended:

  • CREATE INDEX idx_events_demo ON events (id) WHERE is_demo = true; — fast lookup of demo events without scanning the full table
  • CREATE INDEX idx_orders_demo ON orders (created_at) WHERE is_demo = true; — fast cleanup cron queries

---

New Environment Variables

VariableVisibilityPurpose
STRIPE_TEST_SECRET_KEYServer-onlyStripe test secret key for demo checkout sessions
STRIPE_TEST_WEBHOOK_SECRETServer-onlyWebhook signing secret for Stripe test-mode events
NEXT_PUBLIC_STRIPE_TEST_PUBLISHABLE_KEYPublicStripe test publishable key — sent to client for Stripe.js redirect

These may already exist in .env.local for development. They need to be added to Vercel environment variables for production.

---

New Cron Jobs

JobScheduleDescription
demo-event-date-refreshDaily 02:00 UTCRolls starts_at/ends_at forward when the event is <21 days away, keeping it perpetually upcoming
demo-order-cleanupDaily 03:00 UTCDeletes demo orders + tickets older than 7 days

Cron count after this feature: 31.

---

Affected Existing Systems (Audit Checklist for Claude Code)

The following locations must be audited and updated to add is_demo guards. This list is not exhaustive — Claude Code should grep for all orders and events table queries and verify each one:

  • [ ] GET /api/v1/events — exclude demo events from public API list
  • [ ] GET /api/v1/public/events — exclude demo events from unauthenticated list
  • [ ] POST /api/checkout/session — branch on is_demo to use test keys + rate limiting
  • [ ] POST /api/webhooks/stripe — branch on livemode === false for demo fulfillment
  • [ ] POST /api/orders/[id]/refund — block refund on demo orders
  • [ ] Ticket cap enforcement queries — exclude is_demo = true orders
  • [ ] admin-mrr-snapshot cron — exclude demo orders from MRR calculation
  • [ ] org-health-scorer cron — exclude demo orders from health metrics
  • [ ] counter-reconciliation cron (if applicable) — exclude demo tickets
  • [ ] Audience / buyer-summary queries — exclude demo orders
  • [ ] ai-triage-tickets cron — skip support tickets referencing demo orders (P1)
  • [ ] Admin support console — badge demo-sourced tickets (P1)
  • [ ] Admin console — add /admin/demo-event editor page
  • [ ] Internal event PATCH route — allow edits from admin session even when is_demo = true; block from organizer session

---

Open Questions

  1. [Drew] Should the demo event be owned by the platform owner account (Drew's org), or be ownerless / assigned to a special is_platform = true org? The simplest approach is to assign it to Drew's own org with is_demo = true as the guard — no special org type needed.
  2. [Drew] What should the demo event look like content-wise? A generic "The Equaticket Demo Show" works, but a comedy club show or local festival might feel more realistic and relatable for target customers. Decide before implementation so the seed data is final.
  3. ~~[Drew] Should the demo event date roll forward automatically, or use a fixed far-future date?~~ Resolved: Rolling forward via demo-event-date-refresh cron. Date is always 14–30 days in the future, keeping the event looking realistic. starts_at/ends_at are not admin-editable (managed by cron only).
  4. [Engineering] The livemode branch in the Stripe webhook handler — should this be a separate handler function in the same file, or extracted to src/modules/demo/handle-demo-webhook.ts for clarity? Recommend the latter given the existing module structure.
  5. [Engineering] Confirm that sendEmailWithOverride works correctly when called from a demo webhook context (i.e., no organizer-specific BYO email override applies — the demo event falls back to platform Resend key). This should be the case for a Free-tier event owner, but verify.

---

Success Metrics

Leading indicators (visible within days of launch)

  • Demo event page views (via Vercel Analytics + UTM tracking)
  • Demo checkout initiation rate (page views → "Get Tickets" click)
  • Demo checkout completion rate (initiated → checkout.session.completed webhook received)
  • Demo ticket email delivery rate (completed → Resend successful send)

Lagging indicators (visible within 4–8 weeks)

  • Demo-to-signup conversion rate: % of users who visit /events/equaticket-demo and subsequently create an account (attributable via referral_source tracking already in place)
  • Demo-to-paid conversion rate: % of demo users who sign up and later upgrade to a paid plan
  • Target: demo-to-signup rate ≥ 15% within 60 days of launch

---

Timeline Considerations

  • No hard external deadline. This is a conversion optimization feature.
  • Recommended sequencing: implement P0 requirements as a single PR. P1 (post-purchase CTA, UTM tagging) can ship in a follow-up PR without blocking launch.
  • Dependency: STRIPE_TEST_SECRET_KEY and STRIPE_TEST_WEBHOOK_SECRET must be added to Vercel before deploying to production. Verify these exist in the Stripe dashboard (test mode → Developers → API Keys / Webhooks) before starting implementation.

Still need help?

If this article didn't answer your question, our support team is here.

Contact Support

Was this article helpful?

Still need help? Contact Support

Trying the Interactive Demo — Equaticket Organizer Help