Skip to content

Sales and analytics

Every event has its own Sales page with all the numbers you need to understand what’s happening: how much you sold, where the buyers came from, which tier sells best, how many QRs already entered the venue.

How to get there

From three places in the dashboard:

  • In Events (the list), every card has a View sales button.
  • Inside an event editor, top-right next to the status badge.
  • Direct URL: /dashboard/events/<id>/sales.

Only the org owner has access. Anyone else gets 403.

Headline numbers

Four KPIs at the top:

CardWhat it means
Gross revenueSum of totalCents across all paid + fulfilled orders
Service fees collectedWhat you added on top as %. Lands in your Stripe too
OrdersHow many paid orders (independent of ticket count)
Tickets soldSum of items.quantity — one buyer with 4 tickets counts as 4 here

All amounts are formatted in the event currency and the active locale (es-MX, en-US, etc.).

QR status

A card shows the count by status:

  • Issued — QR generated and delivered to the buyer, not yet validated.
  • Validated — successfully scanned by an authorized device.
  • Voided — manually invalidated (admin) or by anti-fraud.
  • Refunded — order was reverted; the QR no longer enters.

During the event this tells you how many people entered (validated) vs how many are still pending (issued - validated).

Channel attribution (UTM)

Any link to your event can carry ?utm_source=<channel>&utm_medium=<medium>&utm_campaign=<campaign>. Boletra’s parser stores those parameters on the order, and the Sales page aggregates them in a table:

ColumnValue
Sourceutm_source (instagram, x, mailchimp, …)
Mediumutm_medium (story, post, email, paid_ads, …)
RevenueSum of totalCents for that combination
OrdersHow many orders that combination generated

Without UTMs everything falls into direct. Some useful patterns:

  • ?utm_source=instagram&utm_medium=story
  • ?utm_source=mailchimp&utm_medium=email&utm_campaign=presale
  • ?utm_source=ig&utm_medium=paid&utm_campaign=launch

Orders list

At the bottom there’s the paginated list of recent orders, sorted newest first. Per row you see:

  • Buyer — name + email + status badge (pending / paid / fulfilled / refunded / failed).
  • Tickets — breakdown quantity × tier name.
  • Channel — UTM source + medium (or if direct).
  • Total — what the buyer paid (including service fee if any).
  • Date — local timestamp.

The input at the top filters by buyer email or name with a 300ms debounce (doesn’t hammer the API on every keystroke). Useful to find the order from someone reporting an issue.

Filters

The pills filter by status:

  • All — default view.
  • Paid — Stripe captured the payment but QRs aren’t issued yet (typically lasts milliseconds in prod).
  • Fulfilled — paid + QRs issued + credits debited + email sent.
  • Refunded — reverted via Stripe; the buyer can’t enter.
  • Failed — Stripe declined the charge or the flow broke.

Mobile vs desktop

  • Mobile: every order is a vertical card with stacked email and items.
  • Desktop (sm: and up): table with columns, horizontal scroll if it overflows.

Pagination is 25 per page by default. Use Load more / Previous at the bottom.

How these numbers populate

EventWhat happens
Buyer pays via StripeOrder becomes paid, webhook checkout.session.completed fires
Webhook → fulfillOrder()Issues QRs, debits 1 credit per ticket (free first), increments tier sold
Order becomes fulfilledShows up in KPIs and the list — the buyer receives the email
Refund via StripeWebhook marks the order refunded and QRs as refunded (no longer enter)
Staff validates QR at the doorThe validated count in the QR card goes up

You don’t need to refresh: React Query refetches every 15s in the background. For a manual pull, reload the browser or switch a filter.