Google Play

    Subscription Grace Periods and Billing Retry States

    A subscription state machine showing transitions between active, grace period, on hold, paused, cancelled and expired.

    A subscription that fails to renew is not immediately a cancelled subscription. There is a sequence of states between a declined payment and a lost customer, and apps that treat renewal as binary either cut off paying users too early or keep serving people who stopped paying weeks ago.

    Short answer

    Google documents the recovery states in its subscriptions guidance: when a renewal payment fails, a subscription can enter a grace period during which the user keeps access while the payment is retried, and if that fails it moves to an on-hold state where access should be suspended while recovery continues. Apple's equivalent flow is exposed through StoreKit and the server APIs. Both platforms expect your app to distinguish these states rather than treating any non-active status as cancelled.

    The states a subscription actually moves through

    Active. Paid, current, nothing to do.

    Grace period. The renewal payment failed and the platform is retrying while the user keeps access. The intent is to avoid punishing a customer for an expired card, and the correct behaviour is to continue serving the subscription while prompting them to fix the payment method.

    On hold. Recovery has not succeeded within the grace window. Access should stop, and the subscription is not yet cancelled, because the platform may still recover it. Users in this state frequently do not know anything is wrong.

    Paused, where the platform supports it. A deliberate user action, with a defined resume date. Access stops and the relationship continues.

    Cancelled but not expired. The user turned off renewal and has paid through the end of the current period. Access continues until the period ends, and treating cancellation as immediate loss is a common and expensive mistake.

    Expired. The period ended without renewal. Access stops.

    Revoked or refunded. The purchase was reversed, sometimes retroactively, and access should stop immediately regardless of the period.

    Seven states, and most implementations model two.

    What goes wrong with a binary model

    Cutting off users during grace. A customer whose card expired opens the app, finds their content gone, and concludes the product is unreliable. The payment usually recovers a day or two later, and by then the impression is formed. This is the most expensive failure in the list because it hits paying customers.

    Continuing to serve users on hold. The mirror image, and the one finance notices. An entitlement system that only checks at purchase time serves content indefinitely to accounts that stopped paying.

    Treating cancellation as immediate. A user who cancels on day two of a monthly period has paid for the month. Removing access immediately is a support complaint and, depending on jurisdiction, worse than that.

    Ignoring revocation. Refunds arrive after the fact, sometimes long after, and an app that never processes them accumulates users with free access and no record of why.

    Reacting to the client. Any of these states reported by the app rather than confirmed with the platform can be reported incorrectly, deliberately or otherwise.

    Building the state machine properly

    Model the states explicitly. A single boolean for whether the user is subscribed cannot express grace or on hold, so the entitlement record needs a status field with the real values and an expiry the server evaluates.

    Consume server notifications rather than polling. Both platforms will tell your backend when a subscription changes state, and that is how you learn about a failed renewal within minutes rather than at the user's next launch. Polling on launch means a lapsed user who does not open the app is invisible to you.

    Decide what each state grants, in writing. Grace keeps full access; on hold keeps the account and removes premium features; cancelled but not expired keeps everything until the date; expired reverts to free. Those decisions belong in a table someone can read, not distributed through conditionals.

    Communicate in-app during grace and hold. The platform sends its own messaging and users miss it. A clear, non-alarming prompt with a direct path to update the payment method is the single most effective recovery mechanism available to you, and it is entirely within your control.

    Reconcile periodically. Notifications get missed, endpoints have outages, and a nightly reconciliation against the platform's view of your subscribers catches the drift before it becomes an accounting problem.

    The messaging that recovers subscriptions

    This part is product work rather than engineering, and it determines how much of the grace period is actually useful.

    Say what happened plainly. A message saying the payment did not go through and access continues until a given date is more effective than a generic billing notice, because it tells the user both the problem and the deadline.

    Link directly to the platform's payment settings. Every extra step loses people, and the path from your prompt to the correct screen should be one tap.

    Do not degrade the experience during grace. The point of grace is that the customer does not notice a service change; adding friction defeats it.

    Escalate at the transition to hold. When access does stop, the message should explain that it is a payment problem rather than a cancellation, because users who believe they cancelled do not try to fix anything.

    And stop messaging after the subscription genuinely ends. Continuing to prompt a former customer is a poor experience and, in some jurisdictions, a marketing communication with its own rules.

    Where the client can undermine it

    The state machine belongs on your server, and the client still has ways to break it.

    Caching entitlement indefinitely, so that a user who moved to on hold retains access until they reinstall.

    Evaluating expiry against the device clock, which the user controls.

    Storing subscription status in unprotected local storage where it can be edited directly.

    Shipping a debug flag that grants premium access, which is a build hygiene problem rather than a subscription one and produces the same result.

    Those are visible in a shipped artifact, and an automated pass such as PTKD.com covers that class of finding: sensitive state stored without protection, debuggable builds, secrets and flags left in the bundle. The server-side state machine still needs testing on its own, by driving each transition and confirming the app reflects it.

    Store policy also applies here. Both the App Review Guidelines and Google Play's policies set expectations about subscription disclosure and cancellation, and a technically correct state machine paired with unclear pricing disclosure still fails review.

    Free trials and introductory offers

    Trials sit on top of the same state machine and add their own failure modes, which is why they deserve separate handling rather than being treated as an active subscription.

    A trial that converts is a normal renewal. A trial that fails to convert follows the same recovery path as any failed payment, which surprises teams who assumed a failed conversion simply ends the relationship.

    Eligibility is the harder part. Whether a user qualifies for an introductory offer is determined by the platform based on their purchase history, not by your records, so an app deciding eligibility locally will show offers to people who cannot use them and hide them from people who can.

    Disclosure requirements are strict for trials specifically. Both stores expect the price after the trial, the trial length and the renewal terms to be clear before purchase, and this is a frequent source of rejection independent of the technical implementation.

    And the transition message matters. A user whose trial is about to convert should know, because the alternative is a surprise charge followed by a refund request and, often, a poor review.

    Testing the transitions

    Subscription bugs are hard to find because the states are slow in production, and both platforms provide ways to compress that.

    Use the sandbox and test environments where renewal periods are shortened, so a monthly subscription cycles in minutes and you can drive several renewals in an afternoon.

    Test each transition deliberately rather than only the happy path: successful renewal, failed renewal into grace, grace into hold, recovery from hold back to active, cancellation with time remaining, expiry, and a refund after the fact.

    Confirm what the app shows in each state, not only what the database says. The mismatch usually lives in the client, where a cached value or a missing case in a conditional produces the wrong experience for a correctly recorded state.

    And test the notification path by taking your endpoint offline briefly during a transition, then confirming reconciliation catches what was missed. That failure will happen in production, and knowing whether you recover from it is worth an hour.

    Reporting that reflects the states

    Once the state machine is correct, the numbers your team looks at should use it, and this is where a good implementation quietly stops being useful.

    Counting subscribers as anyone with a non-expired record overstates the figure by including on-hold accounts that will mostly not recover. Counting only active understates it by excluding grace, which mostly does recover.

    The useful split is active plus grace as current, on hold and paused tracked separately as recoverable, and cancelled-but-not-expired tracked as a leading indicator, since it tells you about churn before the revenue moves.

    Recovery rate from grace and from hold is the metric that tells you whether your in-app messaging works, and it is one of the few product changes that shows up directly in revenue without a new feature.

    And reconcile these figures against the platform's own reporting periodically. Disagreement between your subscriber count and the store's is a signal that events were missed, and finding it in a monthly comparison is much better than finding it during a financial review.

    A table of the states and what to grant

    StateAccessMessage to the user
    ActiveFullNone
    Grace periodFullPayment failed, fix it by a given date
    On holdSuspendedPayment problem, not a cancellation
    PausedSuspendedResumes on a known date
    Cancelled, not expiredFull until period endConfirm the end date
    ExpiredNoneOffer to resubscribe, once
    Revoked or refundedNone, immediatelyUsually none

    The value of writing it as a table is that the disagreements surface immediately. Teams routinely find they hold different beliefs about what happens on hold, and a table forces one answer that the code and the support team can both work from.

    What to take away

    A subscription has around seven states, and modelling two of them produces both lost customers and unpaid access.

    Grace period exists to keep paying customers from being punished for an expired card, so keep access on, prompt clearly, and link straight to the platform's payment settings rather than to a generic billing page.

    Recovery rate out of grace and out of hold is worth measuring, because in-app messaging is one of the few changes that moves revenue without shipping a feature.

    On hold means access stops and the relationship continues, which is a different message from cancellation and should read differently to the user.

    Consume server notifications rather than polling, keep the expiry evaluation on your server, and reconcile periodically to catch missed events, because endpoints have outages and a missed transition is invisible until the numbers disagree.

    Report on the states separately as well, since counting every non-expired record as a subscriber overstates the figure and counting only active ones understates it.

    And check the client for the shortcuts that bypass all of it, since an indefinitely cached entitlement or a device-clock expiry check defeats a correct backend.

    Write the state table down before writing the code. Most of the disagreements in this area are not technical, they are two people holding different beliefs about what on hold should do, and the table is what turns that into a single decision the support team can also work from.

    • #subscriptions
    • #billing
    • #grace period
    • #play billing
    • #storekit
    • #entitlement

    Frequently asked questions

    What is a subscription grace period?
    A state after a renewal payment fails during which the platform retries the payment while the user keeps access. Its purpose is to avoid cutting off a paying customer because a card expired. The correct behaviour is to continue serving the subscription and prompt the user to update their payment method, without degrading the experience, since noticeable friction defeats the point of the grace window.
    What does on hold mean and what should the app do?
    On hold is the state after recovery has not succeeded within the grace window. Access should stop, but the subscription is not cancelled, because the platform may still recover it. Users in this state usually do not know anything is wrong, so the message should explain that it is a payment problem rather than a cancellation, with a direct path to fix it.
    Should access end immediately when a user cancels?
    No. Cancelling turns off renewal, and the user has already paid through the end of the current period. Access should continue until that period ends. Treating cancellation as immediate loss is a common mistake that produces support complaints and, depending on the jurisdiction, worse consequences. It is also a modelling error, since cancelled and expired are separate states.
    How should the server learn about subscription changes?
    Through platform server notifications rather than by polling at app launch. Notifications tell your backend within minutes when a subscription changes state, while polling only learns about a lapse when the user next opens the app, which means lapsed users who stop opening it stay invisible. A periodic reconciliation against the platform's view catches notifications that were missed.
    Can subscription state be evaluated on the device?
    Not reliably. Device clocks are adjustable, so expiry evaluated locally ends whenever the user decides, and a locally cached entitlement can keep serving content to an account that moved to on hold. Keep the evaluation on your server and let the client ask what the user is currently entitled to, with a cache lifetime measured in hours rather than indefinitely.

    Keep reading

    Scan your app in minutes

    Upload an APK, AAB, or IPA. PTKD returns an OWASP-aligned report with copy-paste fixes.

    Try PTKD free