●FUNDING — Rork raises $15M, drawing fresh attention to its mobile-first no-code AI positioning●MAX-NATIVE — Rork Max reaches native territory React Native can't: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and on-device Core ML●MOBILE-FIRST — While Bolt and Lovable focus on web apps, Rork builds mobile apps — production-ready from a plain-language description●WWDC — WWDC26 wraps with AI becoming a core OS capability; the iOS 27 generation raises the value of widgets and Live Activities●PRICING — Free to start, paid plans from $25/mo, Rork Max at $200/mo — ship fast on Expo, then go native with Max where it pays off●ALL-APPLE — Rork Max generates pure Swift covering iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage●FUNDING — Rork raises $15M, drawing fresh attention to its mobile-first no-code AI positioning●MAX-NATIVE — Rork Max reaches native territory React Native can't: AR/LiDAR, Metal 3D, widgets, Dynamic Island, Live Activities, HealthKit, and on-device Core ML●MOBILE-FIRST — While Bolt and Lovable focus on web apps, Rork builds mobile apps — production-ready from a plain-language description●WWDC — WWDC26 wraps with AI becoming a core OS capability; the iOS 27 generation raises the value of widgets and Live Activities●PRICING — Free to start, paid plans from $25/mo, Rork Max at $200/mo — ship fast on Expo, then go native with Max where it pays off●ALL-APPLE — Rork Max generates pure Swift covering iPhone, iPad, Apple Watch, Apple TV, Vision Pro, and iMessage
Raising Your Subscription Price: How to Handle Existing Subscribers on the App Store and Google Play
Raise your subscription price without losing existing subscribers: break-even churn math, store consent flows, PRICE_INCREASE and EXPIRED handling, and cohort tracking.
This spring I raised the monthly price of the paid membership on a site I run. The Stripe side of it took about thirty minutes — create a new Price, point the checkout at it, done. What took far longer was the decision that came before any of that: what should happen to the people who were already paying me?
I ended up grandfathering my existing members at the old price. But the moment you try to make the same move in a mobile app, both the decision and the implementation get considerably harder. The App Store and Google Play handle price changes in completely different ways, and mishandling existing subscribers can turn an increase that was supposed to grow revenue into a wave of cancellations. With AI features pushing per-user API costs into more apps every month in 2026, most indie developers will face this decision sooner or later. Here is how I would work through it, from the math to the webhooks.
Before anything else, calculate your break-even extra churn rate
Pricing discussions tend to start with "how much should we raise it?" The question that actually matters comes first: do existing subscribers move to the new price, or stay where they are?
If you apply the new price to existing subscribers, you are trading a higher unit price against the extra cancellations the increase will trigger. For a plan priced at p per month raised by Δp, the increase loses money once the extra churn rate x crosses this line:
x = Δp ÷ (p + Δp)
Raising a $4.80 plan to $6.00 means 1.20 ÷ 6.00 = 0.2, so the change is revenue-positive as long as fewer than 20% of subscribers leave because of it. Put differently, a 25% price increase can survive one in five subscribers walking away. A small simulator makes it easy to plug in your own numbers.
// Break-even simulator for a subscription price increase// Computes the extra churn you can absorb when migrating existing subscriberstype PriceChangePlan = { currentPrice: number; // current monthly price newPrice: number; // price after the change subscribers: number; // current subscriber count baselineChurn: number; // normal monthly churn (e.g. 0.04 = 4%)};function breakEvenExtraChurn(plan: PriceChangePlan): number { const delta = plan.newPrice - plan.currentPrice; return delta / plan.newPrice; // above this rate, the increase loses money}function monthlyRevenueAfter(plan: PriceChangePlan, extraChurn: number): number { const retained = plan.subscribers * (1 - plan.baselineChurn - extraChurn); return Math.round(retained * plan.newPrice);}const plan: PriceChangePlan = { currentPrice: 480, newPrice: 600, subscribers: 800, baselineChurn: 0.04,};console.log(`Break-even extra churn: ${(breakEvenExtraChurn(plan) * 100).toFixed(1)}%`);console.log(`Monthly revenue at 8% extra churn: ${monthlyRevenueAfter(plan, 0.08)}`);// Output:// Break-even extra churn: 20.0%// Monthly revenue at 8% extra churn: 422400
One caveat: the formula only gives you the ceiling. The churn you actually see depends on how the change is communicated and how the consent flow is designed, which is most of the rest of this article. The alternative — raising prices for new subscribers only — carries almost no churn risk, but the revenue gain arrives slowly because it depends entirely on new signups. That slow-but-safe profile is exactly why I grandfathered my own members. Either way, you cannot make the choice properly without understanding what each store does next.
App Store price changes: when consent is required and when it is not
When you edit a subscription price in App Store Connect, the first fork in the road is whether to preserve prices for existing subscribers.
Preserve prices: only new subscribers pay the new amount. Existing subscribers keep renewing at their current price and no consent flow ever starts
Apply to existing subscribers: Apple informs subscribers by email, push notification, and an in-app sheet. Depending on the size of the increase and the region, the change is either applied automatically after notice, or requires explicit consent
As a rule of thumb, Apple allows increases without consent when the change happens no more than once a year, stays within roughly 5 US dollars and 50% for monthly plans (50 dollars and 50% for annual plans), and is permitted by local law. Beyond that envelope — or in regions where consent is legally required — the new price never applies on its own, and a subscriber who has not consented by their renewal date is simply cancelled.
This is the sharpest difference from running subscriptions on Stripe. On the web, applying a new price is largely at your discretion. On the App Store, an unconsented increase ends in an irreversible cancellation. Whether you keep the increase inside the no-consent envelope or deliberately step beyond it should be decided together with the break-even math from the previous section.
The exact thresholds and regional rules shown above are a guideline; the authoritative version is whatever App Store Connect displays at the moment you make the change. Always confirm there before committing.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦Replace the vague fear of losing subscribers with a concrete number by calculating the break-even extra churn rate before touching any store console
✦Handle the very different price change mechanics of the App Store and Google Play, including PRICE_INCREASE and EXPIRED server notifications
✦Avoid the hardcoded price trap and set up reason-tagged cohort tracking so you can tell whether the increase actually paid off
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Track the outcome through ASSN v2: PRICE_INCREASE and EXPIRED
The moment you apply a price change to existing subscribers, App Store Server Notifications V2 starts telling your server how the consent story unfolds. Three events matter.
PRICE_INCREASE with subtype PENDING: the increase requires consent and the subscriber has not responded yet
PRICE_INCREASE with subtype ACCEPTED: the subscriber consented, or a no-consent increase has been announced to them
EXPIRED with subtype PRICE_INCREASE: the renewal date arrived without consent and the subscription has ended
// Handling price change events in an ASSN v2 webhook (Cloudflare Workers + Hono)// decodeNotification verifies the JWS signature and returns the decoded payloadapp.post("/webhooks/app-store", async (c) => { const payload = await decodeNotification(await c.req.text()); const { notificationType, subtype } = payload; const tx = payload.transactionInfo; // decoded signedTransactionInfo switch (notificationType) { case "PRICE_INCREASE": if (subtype === "PENDING") { // Awaiting consent: queue this subscriber for an in-app and email reminder await db.markPriceConsentPending(tx.originalTransactionId); } else if (subtype === "ACCEPTED") { await db.markPriceConsentAccepted(tx.originalTransactionId); } break; case "EXPIRED": if (subtype === "PRICE_INCREASE") { // Churn caused by the price increase: record it separately from normal churn await db.recordChurn(tx.originalTransactionId, "price_increase_declined"); } else { await db.recordChurn(tx.originalTransactionId, "other"); } await db.revokeEntitlement(tx.originalTransactionId); break; } return c.json({ ok: true }); // Apple keeps retrying unless you return 200});// Expected behaviour: PENDING enters the reminder queue, ACCEPTED clears it,// and EXPIRED(PRICE_INCREASE) lands in your churn stats tagged with its reason
The single most important line is the one that records EXPIRED with a reason. If price-increase churn gets mixed into ordinary churn, you permanently lose the ability to answer "how many subscribers did that increase cost us?" If you do not have a webhook endpoint and entitlement revocation pipeline yet, the setup from Your App Won't Notice the Refund — Revoking Entitlements with REFUND Notifications and the Voided Purchases API carries over unchanged — handling PRICE_INCREASE is just two more cases in the same switch statement.
Google Play price changes: notice-only or opt-in depends on the region
On Google Play you change the price on a subscription base plan in the Play Console, but the model differs from Apple's in a few ways.
Whether existing subscribers migrate to the new price is your choice at change time. If you do not migrate them, they remain in a legacy price cohort indefinitely
If you do migrate them, Google notifies subscribers. Depending on the region, the change is either opt-out (applied after notice) or opt-in (explicit agreement is legally required)
In opt-in regions, a subscriber who reaches their renewal date without agreeing is cancelled
On the server you watch Real-time Developer Notifications. Historically, SUBSCRIPTION_PRICE_CHANGE_CONFIRMED (notificationType 8) signalled that a user accepted a price change. Play's pricing flows have been reworked more than once in recent years, though, so treat the current official documentation as the source of truth at implementation time. Writing the handler so that all price change events funnel through one place means a future spec change only has one spot to fix.
If I were running this on my own apps, I would start on both stores with the conservative shape — keep existing subscribers where they are, charge new subscribers the new price — and hold the consent flow in reserve for the day exchange rates or API costs genuinely force the issue. Once a consent flow starts, it cannot be paused, and its failure mode is irreversible cancellation.
The app-side trap: hardcoded prices turn a change into an incident
A price change feels like a store-console operation, but the app itself hides one classic landmine: a paywall with the price hardcoded as a string.
Ask Rork to "build a paywall for a $4.80/month premium plan" and the generated UI may contain the literal text "$4.80/month". Change the price on the store side only, and the paywall now shows the old price while the purchase sheet charges the new one. From the user's perspective, the app is misrepresenting what they will be billed — a reliable way to fill your reviews with one-star complaints, and a discrepancy that can also be flagged during app review.
// Before: the generated hardcoded price (an incident waiting for the next change)<Text style={styles.price}>$4.80/month</Text>// After: always render the localizedPrice fetched from the storeimport { useEffect, useState } from "react";import * as RNIap from "react-native-iap";function PriceLabel({ sku }: { sku: string }) { const [price, setPrice] = useState<string | null>(null); useEffect(() => { RNIap.getSubscriptions({ skus: [sku] }).then((subs) => { // localizedPrice includes the currency symbol and regional pricing if (subs[0]) setPrice(subs[0].localizedPrice); }); }, [sku]); return <Text style={styles.price}>{price ?? "…"}/month</Text>;}// Expected result: a Japanese storefront shows "¥600/month", a US storefront// "$3.99/month" — the new price appears with no app update at all
The same applies with RevenueCat: render the priceString that comes back in your Offerings. Once every displayed price is fetched dynamically, the change itself ships with no app release. Leave even one hardcoded string behind and every future change requires an app review cycle, with a guaranteed window where the store and the UI disagree. On the day you start considering an increase, grep your codebase for currency symbols. I have caught a placeholder price string sitting on a production onboarding screen exactly this way.
Tell existing users before the consent sheet does
Store emails and consent sheets arrive with zero context. Asked cold — "the price is going up, do you agree?" — a fair share of people will defensively tap no. I would always get ahead of that with an in-app notice before the store fires its notifications.
// Show the price change notice only to subscribers who joined before the change dateimport Purchases from "react-native-purchases";const PRICE_CHANGE_DATE = new Date("2026-08-01T00:00:00+09:00");async function shouldShowPriceNotice(): Promise<boolean> { const info = await Purchases.getCustomerInfo(); const ent = info.entitlements.active["premium"]; if (!ent) return false; // nothing for non-subscribers const since = new Date(ent.originalPurchaseDate); return since < PRICE_CHANGE_DATE; // only pre-change subscribers see it}// Expected behaviour: anyone subscribing after Aug 1 sees nothing, while// existing subscribers get the "your renewal changes in September" notice
The notice itself should state the reason for the increase in one honest sentence. My own apps balance AdMob ad revenue against in-app purchases, and for anything carrying AI API costs I have found "this keeps the feature running" lands far better than corporate boilerplate. Pair the notice with a transition offer for long-time subscribers — an offer code that effectively freezes their price for a year takes most of the sting out of the consent decision. The offer code setup in Revenue Flow Design for Rork Max-Released Apps — Offer Codes, Win-Back, and Push Notification Integration works as-is here.
What to watch afterwards: churn by reason-tagged cohort
Once the change is live, track three numbers until at least two renewal cycles have completed.
Consent rate: the share of subscribers still in PENDING. If renewal dates are approaching and PENDING is not shrinking, trigger the in-app and email reminders
Increase-driven churn: subscriptions that ended via EXPIRED with PRICE_INCREASE (App Store) or lapsed consent (Play). Compare it against the break-even ceiling you computed at the start; if it trends above the line, that is your signal to rethink the next planned increase
Conversion at the new price: an increase narrows the front door too. Compare paywall view-to-subscribe conversion before and after the change
Only with all three can you answer whether the increase actually worked. Without reason tags, increase-driven churn dissolves into baseline churn and the question becomes unanswerable. If your subscription state already flows through a single module, adding the reason field is a few lines; if it is scattered across screens, sorting that out first via Designing a Subscription Entitlement State Machine in Rork — Never Misidentify Who Has Access Right Now ends up being the faster path.
Pitfalls that catch people repeatedly
Forgetting the consent deadline, then chasing a phantom churn spike: consent flows resolve weeks later, as cancellations. When churn jumps a month or two after the change, check the EXPIRED subtypes before suspecting a bug — with reason tags in place the answer takes five minutes
Operating Play with Stripe instincts: on the web you apply increases at your own discretion; on the stores, no consent can mean cancellation. The same words, "price change", carry a very different level of irreversibility
Stacking small increases through the year: Apple's no-consent envelope includes "no more than once per year". Quarterly nudges, a perfectly normal web tactic, drop you into the consent-required flow and backfire
Requesting reviews right after the announcement: prompting users for a rating while the increase notice is fresh actively collects one-star reviews. Pause review prompts for two to three weeks on either side of the change
Start by putting your own numbers into the simulator
A price change runs in one direction: math, then the existing-subscriber decision, then the console, then the webhooks, then the measurement. Skip ahead to the console and you end up making the heaviest decision — what happens to the people already paying you — on momentum. Drop your plan's price, subscriber count, and baseline churn into the simulator at the top and look at the break-even churn rate first. Once that number is in front of you, the choice between grandfathering and the consent flow tends to resolve itself.
I am still undecided about whether my own next change will migrate existing subscribers. If you are weighing the same call, I hope this gave you a firmer place to stand.
Share
Thank You for Reading
Rork Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.