DocsRecipes

Recipes

Pricing, packaging, and plan-change analytics

4 min readReviewed July 2026

Pricing analytics explains how customers encounter an offer, understand its packages, begin checkout, and change their commercial relationship over time. Revenue analytics begins with billing facts; pricing analytics begins earlier, with the choices and eligibility that shaped those facts.

Keep product catalog, quote, entitlement, subscription, invoice, and payment state authoritative in the application, billing provider, or CPQ system. GraphJSON should receive versioned offer exposures and completed transitions for analysis.

Version the offer#

A label such as pro is insufficient when price, limits, features, and billing interval change. Define an immutable offer version:

{
  "event": "pricing_offer_viewed",
  "offer_view_id": "offer_view_01J...",
  "account_id": "acct_7",
  "surface": "in_product_upgrade",
  "catalog_version": "2026_07_1",
  "offer_set": "self_serve_usd",
  "eligible_plan_codes": ["starter_monthly_v4", "business_monthly_v2"],
  "current_plan_code": "starter_monthly_v4",
  "currency": "usd",
  "billing_country_group": "us",
  "experiment_assignment_id": "assign_81"
}

Do not send display copy, personalized contract text, tax addresses, or unbounded feature lists. A catalog code should resolve to reviewed internal metadata outside the event.

Separate eligibility from exposure#

A customer may be eligible for an offer without seeing it, and a page may render without becoming visible. Model:

eligible
  → offer rendered
  → offer viewed
  → package selected
  → checkout started
  → checkout completed
  → billing transition effective

Use eligibility as the denominator for reach and a verified view as the denominator for offer response. Keep internal, sales-managed, ineligible, and grandfathered accounts explicit.

Model paywalls as decisions#

{
  "event": "entitlement_decision_rendered",
  "decision_id": "ent_91",
  "account_id": "acct_7",
  "requested_capability": "scheduled_exports",
  "current_plan_code": "starter_monthly_v4",
  "decision": "upgrade_required",
  "reason": "capability_not_in_plan",
  "catalog_version": "2026_07_1",
  "surface": "export_dialog"
}

The application entitlement service remains authoritative. GraphJSON does not enforce access or decide which plan unlocks a capability.

Distinguish deliberate upgrade prompts from errors, expired trials, exceeded quotas, and unavailable capabilities.

Track package selection and checkout#

{
  "event": "pricing_package_selected",
  "offer_view_id": "offer_view_01J...",
  "account_id": "acct_7",
  "selected_plan_code": "business_monthly_v2",
  "billing_interval": "month",
  "currency": "usd",
  "listed_amount_minor": 9900,
  "selection_source": "comparison_table"
}
{
  "event": "checkout_completed",
  "checkout_id": "co_61",
  "offer_view_id": "offer_view_01J...",
  "account_id": "acct_7",
  "selected_plan_code": "business_monthly_v2",
  "result": "success",
  "billing_event_id": "stripe:evt_123"
}

Never send payment instruments, full billing addresses, checkout tokens, or provider payloads. A checkout success is not necessarily collected revenue; link it to the later authoritative billing fact.

Represent plan changes as transitions#

{
  "event": "subscription_plan_changed",
  "account_id": "acct_7",
  "subscription_id": "sub_44",
  "previous_plan_code": "starter_monthly_v4",
  "plan_code": "business_monthly_v2",
  "change_type": "upgrade",
  "requested_at": "2026-07-25T18:00:00Z",
  "effective_at": "2026-07-25T18:00:00Z",
  "change_reason": "customer_selected",
  "billing_event_id": "stripe:evt_124"
}

Requested and effective time can differ. A downgrade may take effect at period end, while an upgrade may be immediate. Use effective state for plan-based historical analysis and request time for customer-intent analysis.

Do not infer upgrade or downgrade by comparing price alone. Currency, interval, discount, seat count, and packaging can change the amount.

Model seats and add-ons independently#

Plan, quantity, and add-ons are separate commercial axes:

{
  "event": "subscription_quantity_changed",
  "account_id": "acct_7",
  "subscription_id": "sub_44",
  "item_code": "business_seat_v2",
  "previous_quantity": 10,
  "quantity": 18,
  "change_type": "seat_expansion",
  "effective_at": "2026-07-25T18:00:00Z"
}

For add-ons, preserve item code, effective time, and lifecycle. Do not collapse every expansion into a plan upgrade.

Compare purchased quantity with governed seat-utilization analytics, but do not let GraphJSON determine entitlement.

Query a mature offer funnel#

WITH views AS (
  SELECT
    JSONExtractString(json, 'offer_view_id') AS offer_view_id,
    argMin(JSONExtractString(json, 'account_id'), timestamp) AS account_id,
    argMin(JSONExtractString(json, 'catalog_version'), timestamp) AS catalog_version,
    min(toDateTime(timestamp)) AS viewed_at
  FROM pricing_events
  WHERE JSONExtractString(json, 'event') = 'pricing_offer_viewed'
  GROUP BY offer_view_id
),
checkout AS (
  SELECT
    JSONExtractString(json, 'offer_view_id') AS offer_view_id,
    min(toDateTime(timestamp)) AS checkout_at
  FROM pricing_events
  WHERE
    JSONExtractString(json, 'event') = 'checkout_completed'
    AND JSONExtractString(json, 'result') = 'success'
  GROUP BY offer_view_id
)
SELECT
  views.catalog_version,
  count() AS mature_offer_views,
  uniqExactIf(
    views.account_id,
    checkout.checkout_at >= views.viewed_at
    AND checkout.checkout_at < views.viewed_at + INTERVAL 14 DAY
  ) AS converting_accounts,
  converting_accounts / nullIf(uniqExact(views.account_id), 0)
    AS account_conversion_rate
FROM views
LEFT JOIN checkout USING offer_view_id
WHERE views.viewed_at < now() - INTERVAL 14 DAY
GROUP BY views.catalog_version

This is offer-assisted conversion, not proof that the offer caused conversion. Deduplicate accounts deliberately when one account can view many offers.

Analyze upgrades and downgrades by prior behavior#

Useful pre-change context includes:

  • feature limit encounters
  • active members and seat utilization
  • breadth and frequency of product use
  • account age
  • support or reliability friction
  • trial or catalog version

Use behavior observed before the plan-change request. Current properties can leak future information into the analysis.

Controlled downgrade and cancellation reasons can help prioritize work, but reported reasons are selected and incomplete.

Run pricing experiments carefully#

Assignment, eligibility, exposure, package selection, and billing outcome must be separate facts. Preserve currency, market, catalog version, and randomization unit.

Price experiments have additional risks:

  • customers can communicate across variants
  • sales teams can override offers
  • tax and regional rules affect displayed totals
  • conversion can rise while contribution falls
  • short-term conversion can hide retention or refund effects

Use A/B testing guidance, retain assignment evidence, and keep finance-approved contribution and refund guardrails.

Define the commercial outcomes#

Keep distinct:

  • offer conversion
  • first successful payment
  • recurring run rate
  • collected cash
  • recognized revenue
  • contribution margin
  • expansion and contraction
  • gross and net revenue retention

Follow Revenue and subscription analytics and Multi-currency and FX analytics.

Build the pricing dashboard#

Show:

  1. eligible accounts and offer reach
  2. package selection by catalog version
  3. mature checkout and first-payment conversion
  4. paywall encounters and later upgrade
  5. upgrades, downgrades, interval changes, seats, and add-ons
  6. expansion and contraction by prior product behavior
  7. trial, discount, and grandfathered populations
  8. refund, retention, and contribution guardrails
  9. currency and source freshness

Do not compare catalog versions without accounting for market, traffic, customer mix, and maturity.

Launch checklist#

  • Every displayed offer has an immutable catalog version.
  • Eligibility, render, view, selection, checkout, and billing are separate.
  • Product catalog and entitlement remain authoritative outside GraphJSON.
  • Plan changes preserve request and effective times.
  • Seats, add-ons, intervals, discounts, and plans remain distinct.
  • Money uses minor units and explicit currency.
  • Offer conversion uses a mature, deduplicated denominator.
  • Experiments preserve assignment and commercial guardrails.
  • Product behavior precedes the plan outcome being explained.
  • Billing and finance outcomes reconcile with their authorities.
Need a hand?

Tell us what you’re building and we’ll point you in the right direction.

Contact support