Skip to content

Upgrade Funnel — Analytics Events

These events track the user journey from viewing pricing to completing a subscription. The source property connects the entire funnel, making it possible to attribute conversions back to the feature or page that prompted the upgrade.

User Journey

User sees upgrade prompt --> Visits pricing page --> Clicks subscribe --> Stripe checkout
| | | |
(source set) pricing_page_viewed checkout_started (on Stripe)
|
+----------+-----------+
| |
User pays User cancels
| |
subscription_activated checkout_cancelled
(server-side) (back on pricing page)

Events

1. pricing_page_viewed

The user lands on the pricing page. Not fired if the user is returning from a cancelled Stripe checkout (see checkout_cancelled below).

PropertyDescription
sourceWhere the user came from (see Sources below)

Technical details:

  • Fired from components/PlansPricing/index.js on mount via useEffect, only when checkout_cancelled is not in the URL
  • Uses the useAnalytics hook (client-side, Mixpanel)
  • Handler: lib/analytics/client/analyticsMetrics/pricingMetrics.ts (collectPricingPageViewedMetrics)
  • Source is read from router.query.source

2. checkout_started

The user clicks the subscribe button and is about to be redirected to Stripe.

PropertyDescription
planNameName of the selected plan
priceIdStripe price identifier
intervalBilling period (e.g. month, year)
sourceWhere the user came from

Technical details:

  • Fired from components/SubscribeButton/index.js in the onSubmit handler, before the form POST to /api/stripe/create-checkout-session
  • source is passed down from components/PlansPricing/pricingSelector.js as a prop
  • Uses the useAnalytics hook (client-side, Mixpanel)
  • Handler: lib/analytics/client/analyticsMetrics/pricingMetrics.ts (collectCheckoutStartedMetrics)

3. checkout_cancelled

The user started Stripe checkout but came back without paying. Fired on the pricing page when Stripe redirects the user back after cancellation.

PropertyDescription
sourceWhere the user originally came from

Technical details:

  • Fired from components/PlansPricing/index.js on mount via useEffect, when checkout_cancelled=true is in the URL query params
  • Mutually exclusive with pricing_page_viewed — only one fires per page load
  • The cancel URL is built server-side in pages/api/stripe/create-checkout-session.js and includes the original source
  • Uses the useAnalytics hook (client-side, Mixpanel)
  • Handler: lib/analytics/client/analyticsMetrics/pricingMetrics.ts (collectCheckoutCancelledMetrics)

4. subscription_activated (server-side)

The user completed the Stripe checkout and the subscription is active. This event is fired on the server when Stripe sends the checkout.session.completed webhook.

PropertyDescription
accountIdCompany identifier (encrypted)
sourceWhere the user came from (read from Stripe session metadata)

Technical details:

  • Fired from pages/api/stripe/webhook/[location].js inside the checkout.session.completed case
  • Source is read from session.metadata.source (set during checkout session creation), defaults to direct
  • Uses Mixpanel.init() server-side with the MIXPANEL_TOKEN env variable (via getEnvVariables)
  • Wrapped in the Try utility for error handling — failures are reported to Sentry without crashing the webhook
  • Handler: lib/analytics/server/analyticsMetrics/subscriptionMetrics.ts (collectSubscriptionMetrics)

Sources

The source value tells you what prompted the user to visit the pricing page. It is set when the user navigates to /pricing?source=xxx and flows through the entire funnel, all the way to the Stripe webhook.

SourceWhere it comes from
offer_generatorThe upgrade prompt shown in the Offer Generator when the trial is active or expired
billing_pageThe billing/plan section in account settings
settingsThe settings page
premium_conversionA premium feature upsell prompt
expiry_bannerThe warning banner shown when a plan is about to expire or has expired
plan_expiredAutomatic redirect when the user’s plan has expired
widget_plansThe booking widget plans page
directThe user navigated to the pricing page directly (no source param)

Technical details:

  • Sources are defined in lib/analytics/client/events.ts as the PRICING_SOURCES constant
  • Navigation URLs are built using getPricingRoute(source) from lib/routes/index.js
  • Source flows: query param -> hidden form field in SubscribeButton -> metadata.source in Stripe session -> webhook reads it back

Example Queries (Mixpanel)

Conversion rate by source: How many users who viewed pricing actually subscribed?

  • Funnel: pricing_page_viewed -> checkout_started -> subscription_activated, broken down by source

Offer Generator trial conversion: Do trial users of the Offer Generator convert to paid?

  • Filter pricing_page_viewed where source = offer_generator, then measure funnel to subscription_activated

Checkout drop-off: How many users start checkout but don’t finish?

  • Funnel: checkout_started -> subscription_activated, measure drop-off rate

Best-performing upgrade prompt: Which source drives the most conversions?

  • Count subscription_activated broken down by source