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).
| Property | Description |
|---|---|
source | Where the user came from (see Sources below) |
Technical details:
- Fired from
components/PlansPricing/index.json mount viauseEffect, only whencheckout_cancelledis not in the URL - Uses the
useAnalyticshook (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.
| Property | Description |
|---|---|
planName | Name of the selected plan |
priceId | Stripe price identifier |
interval | Billing period (e.g. month, year) |
source | Where the user came from |
Technical details:
- Fired from
components/SubscribeButton/index.jsin theonSubmithandler, before the form POST to/api/stripe/create-checkout-session sourceis passed down fromcomponents/PlansPricing/pricingSelector.jsas a prop- Uses the
useAnalyticshook (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.
| Property | Description |
|---|---|
source | Where the user originally came from |
Technical details:
- Fired from
components/PlansPricing/index.json mount viauseEffect, whencheckout_cancelled=trueis 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.jsand includes the originalsource - Uses the
useAnalyticshook (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.
| Property | Description |
|---|---|
accountId | Company identifier (encrypted) |
source | Where the user came from (read from Stripe session metadata) |
Technical details:
- Fired from
pages/api/stripe/webhook/[location].jsinside thecheckout.session.completedcase - Source is read from
session.metadata.source(set during checkout session creation), defaults todirect - Uses
Mixpanel.init()server-side with theMIXPANEL_TOKENenv variable (viagetEnvVariables) - Wrapped in the
Tryutility 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.
| Source | Where it comes from |
|---|---|
offer_generator | The upgrade prompt shown in the Offer Generator when the trial is active or expired |
billing_page | The billing/plan section in account settings |
settings | The settings page |
premium_conversion | A premium feature upsell prompt |
expiry_banner | The warning banner shown when a plan is about to expire or has expired |
plan_expired | Automatic redirect when the user’s plan has expired |
widget_plans | The booking widget plans page |
direct | The user navigated to the pricing page directly (no source param) |
Technical details:
- Sources are defined in
lib/analytics/client/events.tsas thePRICING_SOURCESconstant - Navigation URLs are built using
getPricingRoute(source)fromlib/routes/index.js - Source flows: query param -> hidden form field in SubscribeButton ->
metadata.sourcein 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 bysource
Offer Generator trial conversion: Do trial users of the Offer Generator convert to paid?
- Filter
pricing_page_viewedwheresource = offer_generator, then measure funnel tosubscription_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_activatedbroken down bysource