The Problem: You Have Data, But Not Insight

You've shipped your subscription app, revenue is coming in, and RevenueCat is processing every transaction. Then you open your App Store Connect dashboard, your Google Play Console, and your RevenueCat dashboard — and the numbers don't match each other. You're not sure which ones to trust. You don't know if your churn is "good" or "bad." You see MRR but don't know if it's the same as the revenue line you're telling investors about. You want to build a custom dashboard but aren't sure which data fields to use.

This guide cuts through that confusion. You'll learn what every RevenueCat chart actually measures under the hood, why the numbers differ from the app stores (and why that's fine), and how to get your transaction data programmatically for custom analytics workflows.


Part 1: The Charts Ecosystem — What Each Chart Is For

RevenueCat charts are generated from the current snapshot of your purchase receipts, not from client-side event logging. This means historical data can change retroactively — for example, if a user is refunded, charts are updated accordingly. Charts only show production data (not sandbox) and are available on Starter, Pro, and Enterprise plans. Source

Here's a map of the full chart ecosystem and when to reach for each one.

Revenue vs. MRR vs. ARR: Pick the Right Velocity Metric

These three charts are often confused. Use the right one for the right question:

Chart What It Measures When to Use It
Revenue Actual cash collected in a period, minus refunds Day-to-day health; shows real cash flow including consumables, non-renewables
MRR Normalized monthly value of active subscriptions Business velocity; comparing across different billing intervals
ARR MRR × 12 High-level business scale benchmarking

Revenue counts every transaction when it occurs — a single annual subscription renewal for $120 creates a $120 spike on that day. Source This makes it volatile period-over-period when you have a mix of annual and monthly subscribers.

MRR smooths that out by normalizing all subscription durations to a monthly equivalent. A $120/year subscription contributes $10/month to MRR. A $8/month subscription contributes $8/month. This is why MRR is the standard velocity metric for subscription companies. Source

The normalization table is explicit in the docs:

Duration Normalized Monthly Revenue
1 week price × 4
1 month price × 1
3 months price × (1/3)
6 months price × (1/6)
1 year price × (1/12)

⚠️ Pitfall: MRR includes active subscriptions even if auto-renew is disabled. A subscriber who cancels but hasn't expired yet still contributes to MRR. This means MRR can temporarily overstate future revenue. Source Use the Subscription Status chart to break down how much of your MRR comes from healthy vs. about-to-expire subscriptions.

All three charts support three revenue views: Revenue (gross), Revenue net of taxes, and Proceeds (after store commission). Use Proceeds when estimating what you'll actually receive. Source


Active Subscriptions: Your Subscriber Count

Active Subscriptions counts unique paid, unexpired subscriptions at the end of each period. Cancelled-but-not-yet-expired subscriptions still count as active. Source

⚠️ Pitfall (Google Play): Google considers trials to be active subscriptions. RevenueCat does not — trials are tracked separately in Active Trials charts. This is one of the most common sources of discrepancy between Play Console numbers and RevenueCat numbers. Source

Use Active Subscriptions Movement to understand the flow — how many new subscriptions were added and how many churned in a period — rather than just the snapshot count.


Churn vs. Subscription Retention: Two Different Lenses on the Same Problem

These two charts answer different questions and should not be used interchangeably.

Churn is a period-level rate:

Churn Rate = (Churned Actives / Actives at start of period) × 100

A subscription is considered "churned" only when it expires — not when auto-renew is turned off. Churn can go negative if resubscriptions outnumber expirations. Source

Subscription Retention is cohort-level. It shows what percentage of subscriptions that started in a given cohort are still renewing after 1, 2, 3... N periods. It's cohorted by first purchase date, not first seen date. Source

When to use which: Use Churn as a rolling business health metric — is it stable, rising, or falling? Use Subscription Retention when you want to compare how different cohorts (e.g., monthly vs. annual subscribers, users from different countries) perform over time, or when you need a retention curve for LTV modeling.

Key nuance: Retention is always calculated relative to the initial cohort size, not the prior period. Month 3 retention of 60% means 60% of the original cohort is still active — not 60% of whoever was active at month 2. Source


LTV: Realized, Cohorted, and Predicted

RevenueCat provides three distinct LTV perspectives — understanding the difference is critical:

Realized LTV per Customer shows actual revenue generated by a customer cohort (cohorted by first seen date), divided by customer count. You can set a "Customer Lifetime" window (e.g., 30 days, 90 days, or unbounded). Source

⚠️ Incomplete Periods Pitfall: If you set Customer Lifetime to 90 days, all cohorts less than 90 days old are marked as incomplete and should not be used for comparisons. The chart makes this explicit — but it's easy to miss when you're looking at "last 3 months" data. Source

The Cohort Explorer takes this further, letting you measure Revenue, Proceeds, Realized LTV, or Retained Subscriptions across cohort types — New Customers, Initial Conversions, or New Paying Customers — in a matrix view. Source

The Prediction Explorer adds forward-looking estimates. RevenueCat predicts up to 24-month LTV for paid subscriptions (and, as of January 2026, trial subscriptions weighted by conversion probability) using survival curves built from data across 50k+ apps and 450M+ subscriptions. Source

Predicted LTV = Realized LTV + predicted future revenue from active/trial subscriptions in the cohort.


Conversion Charts: Measuring Your Acquisition Funnel

RevenueCat offers several conversion charts, each measuring a different stage:

  • Initial Conversion: First conversion to any product (free trial counts)
  • Conversion to Paying: Portion of new customers who make a payment within a configurable timeframe
  • Trial Conversion: Portion of trials that convert to paid

The Conversion Timeframe selector is essential for fair comparisons. If you set it to 7 days and compare a cohort that's 30 days old with one that's 5 days old, the 5-day cohort is marked incomplete — it hasn't had its full 7 days to convert yet. Without this flag, you'd wrongly conclude newer cohorts are converting worse. Source


Part 2: Charts v3 — Real-Time and New Dimensions

Charts v3 (currently in beta) brings major improvements. Enable it by toggling Charts v3 in your dashboard. Source

What's New

Real-time updates: Charts previously refreshed every 2–12 hours. Charts v3 updates almost all charts in real-time, enabling intra-day monitoring for launches, A/B test results, or traffic spikes.

New and improved dimensions for filtering and segmenting: - Custom Attributes — segment by your own user properties - Ad Attribution — connect acquisition source to subscription performance - Platform — now reports a customer's first seen platform (not last seen), making acquisition analysis more reliable - Country — now uses the store account country when available, falling back to IP-based location

Period-over-Period comparisons: Compare any period against the prior equivalent period directly in the chart UI.

Improved refund behavior: In Charts v3, refunds no longer retroactively change metrics for completed periods. Instead, a refunded transaction stays counted in the original period, and the refund is deducted on the date it actually occurred. This gives you a more accurate picture of what actually happened in each period. Source

Unified subscription model: Charts v3 normalizes store-specific behaviors. Product changes and resubscriptions now each create distinct subscriptions, making cross-store metrics consistent. Source

⚠️ Caveat: At launch, Charts v3 supports App Store, Play Store, Stripe, and RevenueCat Web Billing. Amazon, Roku, and Paddle support is coming. Source


Part 3: Why Your Numbers Don't Match the App Stores

This is one of the most common developer pain points. The short answer: different sources use different definitions by design. Source

The Four Main Causes of Discrepancy

1. Transaction date vs. settlement date RevenueCat records revenue on the transaction date (when the purchase was initiated or renewal was due). Apple's financial reports use the settlement date (when Apple actually received the payment). A renewal due November 29 that fails and retries successfully on December 4 during the grace period appears in December's Apple financial report, but in November's RevenueCat data. Source

2. Calendar months vs. Apple's fiscal calendar Apple's fiscal months don't align with calendar months. January 2025's Apple fiscal month runs from December 29, 2024, to February 1, 2025. If you're comparing RevenueCat's January data to Apple's January financial report, you're comparing different time windows. Source

3. Trial definitions Google counts trials as active subscriptions. RevenueCat tracks them separately. Comparing "Active Subscriptions" between the two will always show a gap on Android apps with free trials.

4. Only data sent through RevenueCat is counted If you migrated an existing app to RevenueCat, only receipts that have been sent through the RevenueCat SDK or API are included in charts. Historical transactions that were never sent won't appear. Source

Rule of thumb: Use RevenueCat charts for business trends and subscriber behavior. Use Apple/Google financial reports for tax reporting and payout reconciliation. Never use RevenueCat data for tax filings. Source


Part 4: Programmatic Access via Scheduled Data Exports

For developers who want to build custom dashboards, run their own SQL queries, or feed subscription data into a data warehouse, Scheduled Data Exports are the right tool. RevenueCat delivers gzip-compressed CSV files of all your transaction data daily to S3, GCS, or Azure Blob Storage. Source

Available on Grow, Pro, and Enterprise plans (and all plans signed up after September 2023). Enterprise plans can receive exports more frequently than once per day. Source

Setting Up S3 Delivery

  1. In AWS, create an IAM policy granting s3:ListBucket, s3:GetObject, s3:PutObject, and s3:DeleteObject on your target bucket.
  2. Create an IAM user attached to that policy and generate an access key.
  3. In RevenueCat: Project Settings → Integrations → Scheduled Data Exports → Amazon S3, enter your Access Key ID, Secret Access Key, and bucket name.
  4. Allow up to 24 hours for the first delivery. Source

You can choose between a full daily export or incremental exports (new and updated transactions only since the last delivery). The first delivery is always a full export regardless of this setting. Source

Key Fields in Data Export Version 5

The current version (v5) is the most feature-complete. Key fields for analytics: Source

Field Description
rc_original_app_user_id Unique user identifier across all transactions
product_identifier Product SKU purchased
product_duration Standard duration (e.g., P1M = 1 month, ISO 8601)
is_trial_period true if transaction was a free trial
price_in_usd Revenue after refunds (in USD)
purchase_price_in_usd Gross revenue before refunds
start_time / end_time Subscription period boundaries
first_seen_time When RevenueCat first saw the customer (used for cohorts)
country Store country or IP-estimated country
offer / offer_type Promotional offer details (new in v5)
auto_resume_time When a paused Play Store subscription will resume (new in v5)

Working with Exports in Python

Here's a practical workflow for downloading and analyzing your daily export:

import boto3
import gzip
import pandas as pd
from io import BytesIO
from datetime import date, timedelta

# --- Download today's export from S3 ---
s3 = boto3.client(
    's3',
    aws_access_key_id='YOUR_ACCESS_KEY',
    aws_secret_access_key='YOUR_SECRET_KEY',
)

BUCKET = 'your-revenuecat-bucket'
# RevenueCat delivers files named by date, e.g.:
# revenuecat_transactions_YYYY-MM-DD.csv.gz
today = date.today().isoformat()
key = f'revenuecat_transactions_{today}.csv.gz'

obj = s3.get_object(Bucket=BUCKET, Key=key)
with gzip.open(BytesIO(obj['Body'].read()), 'rt', encoding='utf-8') as f:
    df = pd.read_csv(f)

# --- Filter to paid, non-trial transactions only ---
paid = df[
    (df['is_trial_period'] == False) &
    (df['price_in_usd'] > 0)
].copy()

# --- Compute a simple MRR snapshot ---
# Normalize each active subscription to monthly value
duration_multipliers = {
    'P1D': 30, 'P3D': 10, 'P7D': 4, 'P14D': 2,
    'P4W': 1,  'P1M': 1,  'P2M': 0.5, 'P3M': 1/3,
    'P6M': 1/6,'P1Y': 1/12,
}

# Keep only currently active subscriptions (end_time in the future)
now = pd.Timestamp.utcnow()
paid['end_time'] = pd.to_datetime(paid['end_time'], utc=True)
active = paid[paid['end_time'] > now].copy()

# Get the most recent transaction per user per product
active = active.sort_values('start_time').groupby(
    ['rc_original_app_user_id', 'product_identifier']
).last().reset_index()

active['mrr_contribution'] = active.apply(
    lambda row: row['purchase_price_in_usd'] * duration_multipliers.get(
        row['product_duration'], 1
    ),
    axis=1
)

total_mrr = active['mrr_contribution'].sum()
print(f"Estimated MRR (USD): ${total_mrr:,.2f}")
print(f"Active paid subscriptions: {len(active):,}")

# --- Simple churn analysis: new vs expired in last 30 days ---
thirty_days_ago = now - pd.Timedelta(days=30)
paid['start_time'] = pd.to_datetime(paid['start_time'], utc=True)

new_subs = paid[paid['start_time'] >= thirty_days_ago]
expired_subs = paid[
    (paid['end_time'] >= thirty_days_ago) &
    (paid['end_time'] < now)
]

print(f"\nLast 30 days:")
print(f"  New paid subscriptions: {len(new_subs):,}")
print(f"  Expired subscriptions:  {len(expired_subs):,}")

# --- Revenue by product (last 30 days) ---
recent_revenue = paid[paid['start_time'] >= thirty_days_ago]
revenue_by_product = (
    recent_revenue.groupby('product_identifier')['price_in_usd']
    .sum()
    .sort_values(ascending=False)
)
print("\nRevenue by product (last 30 days):")
print(revenue_by_product.to_string())

⚠️ Pitfall with price_in_usd vs purchase_price_in_usd: price_in_usd reflects revenue after refunds (a refunded transaction shows $0). purchase_price_in_usd holds the original gross price even after a refund. Use price_in_usd for net revenue analysis; use purchase_price_in_usd when you need to see gross revenue and track refunds separately. Source


Part 5: Practical Analytics Workflows — What to Watch When

For Solo/Indie Developers (Weekly Check-in)

  1. MRR trend (last 3 months, monthly resolution) — Is the line going up? Flat? Declining?
  2. Churn rate — Is it stable? Segment by product duration: annual subscribers almost always churn less than monthly.
  3. Conversion to Paying (7-day conversion window) — Is your trial-to-paid funnel converting consistently? A sudden drop often signals a paywall or onboarding issue, not a pricing problem.
  4. Revenue chart → New vs. Renewal split — A healthy business has renewal revenue growing as a share. If you're >6 months old and still mostly new revenue, churn may be masking a retention problem.

For Growth Teams (Daily / On-Demand)

  • Charts v3 real-time mode during a launch or pricing experiment — watch Active Subscriptions and Revenue update live.
  • Cohort Explorer: Compare Realized LTV/Customer for your last 6 monthly cohorts. Are newer cohorts performing better or worse than older ones at the same age?
  • Prediction Explorer: Use predicted 12-month LTV to validate whether a new acquisition campaign is bringing in customers whose expected value justifies the CAC.
  • Subscription Retention, segmented by Country and Product Duration — find your best-performing segments and double down.

For Data Teams (Scheduled Export Consumers)

  • Set up incremental exports to reduce daily file size and processing time.
  • Join rc_original_app_user_id with your own user tables using rc_last_seen_app_user_id_alias as a fallback for aliased users.
  • Use first_seen_time for cohort definitions — this aligns with how RevenueCat calculates Conversion Rate and LTV charts, so your custom queries will match the dashboard. Source
  • Build retention curves by cohortting on first_seen_time and tracking the sequence of start_time transactions per user — a user with N renewals has a subscription depth of N.

Quick Reference: Common Pitfalls

Pitfall What's Actually Happening Fix
RevenueCat MRR ≠ App Store revenue Different metric definitions (normalized vs. cash collected, calendar vs. fiscal month) Use Revenue chart for cash comparison; use MRR for velocity
"My churn looks low but subscribers are leaving" Auto-renewal disabled subs aren't churned until expiry Check Active Subscriptions Movement for cancellation trends
Recent cohorts show lower LTV Incomplete periods — cohorts haven't had time to mature Filter to cohorts older than your Customer Lifetime window
Export revenue doesn't match dashboard Using purchase_price_in_usd (gross) vs price_in_usd (net of refunds) Pick the right field for your use case
Google subscriber count differs significantly Google counts trials; RevenueCat doesn't Compare Active Paid Subscriptions, not total Active Subscriptions
Historical charts change day-to-day Charts are generated from live receipt snapshots; refunds/corrections update history Expected behavior; use exports for point-in-time snapshots

Sources