Revenue so far this month is easy to see. Where the month will land is harder, and for a small subscription app most of the answer is already on a calendar: each live subscription has a renewal date, a price and a state (still set to renew, or cancelled). RevenueCat's webhook sends all three. This post builds a month forecast from that schedule so you can build it on your own webhook table, then shows how it did when I backtested it on LuminaClean, my photo cleaner app.
What Apple's reports and RevenueCat's charts already cover
Apple's numbers arrive too late to steer a month. The financial report for a fiscal month comes by the first Friday of the next fiscal month (Apple, financial reports), and the payment within 45 days of that fiscal month's end (Apple, receiving payments). Sales and Trends is daily, but it reports what sold, not what's coming. The full timeline is in Apple payout vs App Store proceeds vs RevenueCat revenue.
RevenueCat's charts update in real time on most charts (Charts overview) and cover what came in: Revenue, MRR, Active Subscriptions. Two of them come close to a forecast:
- Subscription Status splits active subscriptions into set to renew, set to cancel, billing issue and unknown (docs). That's the raw input for the renewal part below.
- Prediction Explorer predicts revenue per cohort for up to 24 months from the first purchase, from survival curves and auto-renew status, and counts unconverted trials weighted by their chance of converting (docs).
Those are the same ingredients this method uses, arranged by cohort. As of September 2026 RevenueCat's list of charts has no forecast for the current calendar month, so that arithmetic is yours to do.
The data you need from the webhook
Store every webhook event with its raw payload. The fields this method reads, per RevenueCat's event types and fields:
type:INITIAL_PURCHASE,RENEWAL,CANCELLATION,UNCANCELLATION,NON_RENEWING_PURCHASE,PRODUCT_CHANGE,SUBSCRIPTION_EXTENDED,EXPIRATION.app_user_idandproduct_id, which together identify one subscription.price: USD,0for free trials and negative for refunds.purchased_at_msandexpiration_at_ms. The difference is the period length, which tells you the plan: under 10 days is weekly, under 45 monthly, anything longer yearly (add a class if you sell quarterly or six-month plans).period_type:TRIALmarks a trial.is_trial_conversiononRENEWAL: the previous period was a free trial.environment(keepPRODUCTION) andis_family_share(leave Family Sharing events out).
Also drop your own test devices and, if you can detect them, pirated copies. What each event means for access, as opposed to revenue, is covered in RevenueCat webhook events vs entitlements.
The forecast in one block
forecast = booked this month (payments minus refunds so far)
+ sum over renewals due before month end:
last paid price x P(renews | plan, days to renewal)
+ sum over trials ending before month end:
typical price x P(converts | days to trial end)
+ new-buyer pace x days left
- refund pace x days left
new-buyer pace = 0.5 x mean(daily new-buyer money, last 56 days)
+ 0.5 x median(weekly new-buyer money, last 8 weeks) / 7
80% range = forecast +/- 1.2816 x sqrt(
sum of p(1 - p) x price^2 over renewals and trials
+ variance(daily new-buyer money) x days left)
Work in one timezone and bucket "this month" by it. The steps below go through each line.
Step 1: the revenue already booked
Sum price over this month's production events: positive
prices are payments, negative ones are refunds. This part is known, so
the closer you are to month end, the more of the forecast is fact.
Step 2: renewals, one subscription at a time
List the live subscriptions: for each user and product, take the latest
event. If it's a purchase, renewal, uncancellation, product change or
extension with an expiration still in the future, the subscription is
live and still set to renew. If the latest event is a
CANCELLATION, the user turned off auto-renew and it drops
out. For each live subscription whose expiration falls before month
end, the expected money is its last paid price times the chance it
renews.
Measure the chance by days to renewal
Measure that chance on your own past periods. A period is one paid week
or month; use only periods that ended at least 6 days ago, so billing
retries have had time. A period renewed if a paid RENEWAL
for the same user and product arrived between 2 days before and 6 days
after its end. Then, for each distance to renewal (within 3 days, 4 to
10 days, 11 or more), count the periods that were still not cancelled
at that distance and how many of those renewed.
LuminaClean's numbers as of September 23, 2026:
| Plan | Renewal within 3 days | 4 to 10 days | 11 days or more |
|---|---|---|---|
| Monthly | 99% (30 periods) | 91% (33) | 85% (34) |
| Weekly | 81% (14) | 72% (10) | does not occur |
Distance matters because cancellations bunch up early. In LuminaClean, 63.6% of weekly cancellations came within 24 hours of paying (of 11). For monthly plans it was 30% within 24 hours and 70% in between, with none in the last 3 days before renewal (of 10). A monthly subscription still set to renew two days out has survived the risky part; one three weeks out hasn't.
These are chances for subscriptions that haven't been cancelled yet, which is what a forecast needs. They're much higher than overall renewal rates: across all LuminaClean periods since tracking began, 75.0% of monthly periods renewed (of 40) and 47.8% of weekly ones (of 23). The difference is the cancellations that already happened, and those subscriptions are no longer on your list.
Weekly plans renew more than once
A weekly subscription due on the 10th can renew on the 10th, the 17th, the 24th and the 31st. Each renewal needs the one before it, so the second one's chance is the first one's times the weekly chance again, and so on down the month.
A young app needs a starting point
With no history there's nothing to measure, so start from a default and let the app's own periods take over as they come in:
P = (renewed + 8 x default) / (observed + 8)
The 8 is how many periods the default is worth. After a few dozen of the app's own periods, its history dominates. Pick a realistic default. In my backtest, cautious defaults under-predicted renewals by 15–20% until the app had its own history.
Step 3: trials ending before month end
A trial is a subscription whose latest event has
period_type TRIAL. For each one ending before
month end, the expected money is the plan's typical price (the median
paid price for that product) times the chance a trial converts. Measure
that chance the same way as renewals, from past trials: a trial
converted if a paid RENEWAL followed it, which RevenueCat
marks with is_trial_conversion.
Trials that haven't started yet can still convert inside the month if enough days are left. For those later days, add trial-conversion money at its recent pace, computed like the new-buyer pace below.
Step 4: new buyers, at a pace that survives lumpy weeks
New-buyer money is first purchases that aren't trials, plus non-renewing purchases such as lifetime unlocks. It's the only part of the forecast with no schedule behind it, so it has to be a pace.
The obvious pace, a plain 28-day average, didn't work. It over-predicted by 2x in quiet weeks, because one lumpy week echoes in a 28-day average for a month. In a small app a few yearly buyers are enough to make a week lumpy.
The pace I use is half of each of two numbers. The median of the last 8 weekly totals ignores one or two lumpy weeks. On its own it would also ignore a real change for about a month, because a median of eight only moves once four weeks have moved. The other half, the 56-day daily mean, reacts sooner. Both windows are whole weeks, so every weekday counts the same number of times.
Step 5: refunds
RevenueCat reports a refund as a CANCELLATION with
cancel_reason CUSTOMER_SUPPORT and a negative
price. Take the average daily refund amount over the last
56 days, times the days left, and subtract it. LuminaClean has had 4
refund events since tracking began, all of lifetime purchases made
before tracking, which is too few to say anything about a rate. The
term is there for apps where refunds are a real share of revenue.
Step 6: an 80% range
Each renewal either happens or doesn't, so treat it as a coin with
probability p and value price: its variance is
p(1 - p) x price^2. Sum that over renewals and trials, add
the variance of daily new-buyer money times the days left, and take the
square root. The 80% range is the forecast plus or minus 1.2816 times
that, the z-value that cuts 10% off each side. Don't let the low end go
below the revenue already booked.
The range assumes renewals are independent. They're separate people, so mostly they are, but a payment outage or a price change would hit many at once, and the range doesn't model that. In LuminaClean, 0 of 63 periods failed on payment, so billing trouble hasn't tested it.
The backtest: forecasts made from past dates
To check the method, I ran it as of moments in the past using only the data available then: no later renewals, cancellations or purchases. Then I compared the forecast for the following two weeks, or for the rest of the month, with what came in. Errors below are the forecast minus the actual, as a share of the actual; negative means the forecast came in low. All of it is LuminaClean, with test devices and pirated copies left out.
Two-week windows
| Window starts | Forecast error |
|---|---|
| Aug 13 | +4.6% |
| Aug 17 | −9.0% |
| Aug 21 | −5.5% |
| Aug 25 | −9.6% |
| Aug 29 | +2.0% |
| Sep 2 | −24.6% |
| Sep 6 | −32.6% |
Median absolute error 9.0%, mean 12.6%, and 6 of the 7 outcomes fell inside the 80% range. A well-calibrated 80% range should miss about one time in five, so 6 of 7 is in line with that. The windows start four days apart and each runs 14 days, so they overlap: seven windows are a sanity check rather than seven independent tests.
The two big misses were the windows starting in early September, when new yearly buyers jumped. The new-buyer pace is built to ignore lumpy weeks, so when a real jump starts it lags by design, and a forecast built on the past can't know the jump is coming. Most of the misses were on the low side.
Month-end forecasts
The rest-of-August forecast made on August 15 came in 5.0% low (−5.0%). The one made on August 22 came in 21.6% low (−21.6%). These errors are measured on the part of the month still to come. The revenue already booked is known exactly, so the error on the whole month's total is smaller than the error on what's left. A later forecast isn't automatically a better one: with little more than a week left, a couple of unexpected purchases are a large share of what's being forecast.
The first month after the webhook went live
In early August, the first month after LuminaClean's webhook went live on July 10, forecasts ran about 70% low. The cause was a blind spot: a subscription that hadn't renewed since tracking began had no events, so it wasn't on the list. Take a hypothetical monthly subscriber who last paid on July 5: they don't appear until their August 5 renewal. After one full monthly cycle every live monthly subscription has shown up and the blind spot closes. Yearly subscribers stay invisible until their renewal, up to a year, unless you load their history some other way. If your webhook is new, count how many days of history you have and say so next to the forecast.
What didn't work
- A plain 28-day average for new-buyer money. It over-predicted by 2x in quiet weeks, because one lumpy week echoes for a month. The median blend in step 4 replaced it.
- Cautious default renewal chances. They under-predicted renewals by 15–20% until the app had its own history. The fix was realistic defaults with a small weight, so the app's own periods take over quickly.
Where it breaks
- Jumps in new buyers. Early September's yearly buyers are the example. The pace follows real changes a few weeks late.
- A young webhook history. About 70% low in the first month for LuminaClean.
- Small buckets. The renewal chances rest on 10 to 34 periods each, so each chance has its own error that the 80% range doesn't include.
- Things that hit everyone at once. A price change, a billing outage or App Store featuring break the independence the range assumes.
For how the same app's weekly numbers moved over this period, including the early-September revenue jump, see the weekly numbers I check across four apps.
FAQ
How do you forecast a subscription app's monthly revenue?
Start from the revenue already booked this month. Add each live subscription renewing before month end times its chance of renewing, each trial ending before month end times the conversion chance, and new-buyer money at a recent pace. Subtract expected refunds and add an 80% range.
Can RevenueCat forecast this month's revenue?
Its charts show revenue, MRR and subscription status, most of them in real time, and the Prediction Explorer predicts revenue per cohort for up to 24 months. As of September 2026 its list of charts has no forecast for the current calendar month.
How accurate is a month forecast for a small subscription app?
On LuminaClean over seven overlapping two-week windows: median absolute error 9.0%, mean 12.6%, 6 of 7 outcomes inside the 80% range. In the first month after the webhook went live, about 70% low.
Why does a forecast run low right after connecting RevenueCat webhooks?
Subscriptions that haven't renewed since the webhook went live have no events yet. For monthly plans that closes after one monthly cycle; yearly subscribers stay invisible until their renewal.
Why not use a plain average for new purchases?
A few yearly purchases make single weeks lumpy, and a plain 28-day average lets one lumpy week echo for a month. In LuminaClean's backtest it over-predicted by 2x in quiet weeks.
Sources
- LuminaClean production data and backtest, pulled September 23, 2026, test devices and pirated copies excluded
- RevenueCat, Event types and fields
- RevenueCat, Charts overview, Subscription Status chart, Prediction Explorer
- Apple, Download financial reports and Overview of receiving payments