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
- In AWS, create an IAM policy granting
s3:ListBucket,s3:GetObject,s3:PutObject, ands3:DeleteObjecton your target bucket. - Create an IAM user attached to that policy and generate an access key.
- In RevenueCat: Project Settings → Integrations → Scheduled Data Exports → Amazon S3, enter your Access Key ID, Secret Access Key, and bucket name.
- 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_usdvspurchase_price_in_usd:price_in_usdreflects revenue after refunds (a refunded transaction shows $0).purchase_price_in_usdholds the original gross price even after a refund. Useprice_in_usdfor net revenue analysis; usepurchase_price_in_usdwhen 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)
- MRR trend (last 3 months, monthly resolution) — Is the line going up? Flat? Declining?
- Churn rate — Is it stable? Segment by product duration: annual subscribers almost always churn less than monthly.
- 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.
- 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_idwith your own user tables usingrc_last_seen_app_user_id_aliasas a fallback for aliased users. - Use
first_seen_timefor 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_timeand tracking the sequence ofstart_timetransactions 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
- RevenueCat Charts Overview
- Charts v3 (Beta) — Real-Time Charts
- Monthly Recurring Revenue (MRR) Chart
- Annual Recurring Revenue (ARR) Chart
- Revenue Chart
- Active Subscriptions Chart
- Churn Chart
- Subscription Retention Chart
- Realized LTV per Customer Chart
- Cohort Explorer
- Prediction Explorer
- Conversion to Paying Chart
- Reconciling with App Store Financial Reports
- Scheduled Data Exports Overview
- Amazon S3 Setup
- Data Export Version 5