- Monetization
- App Store
- Google Play
Billing grace period: App Store and Google Play setup
Billing grace period setup for App Store and Google Play subscriptions: durations, account hold, StoreKit and Play Billing code, server events and testing.
AppRanker team14 min read

On this page
A billing grace period keeps a subscriber's access switched on for a few days after a renewal payment fails, while the store keeps trying to charge them. Cards expire, banks decline and balances run low, and none of those people meant to cancel. If the store recovers the payment inside the grace period, the subscriber never notices, keeps their renewal date, and you keep the revenue. If you have no grace period, they lose access on the day of the decline, and many never come back.
Apple and Google both offer one, but they work differently. On the App Store, Billing Grace Period is off until you turn it on. On Google Play, every auto-renewing base plan has a grace period and account hold by default, and you may have shortened them without knowing. This guide covers how each store's billing grace period works, how to set it up, the client and server code that has to respect it, how to test it in the sandbox, and what Google announced about grace periods this autumn. Facts are taken from Apple's Reducing Involuntary Subscriber Churn and Billing Grace Period help pages, and Google's subscription lifecycle documentation.
Key takeaways
- When a renewal fails on the App Store, Apple retries billing for up to 60 days. A billing grace period lets the subscriber keep access for the first 3, 16 or 28 days of that (3 or 6 days for weekly plans).
- App Store Billing Grace Period is off by default. Turn it on in App Store Connect under Subscriptions, sandbox first.
- Google Play gives every auto-renewing base plan a grace period followed by account hold by default. Grace plus hold must total 30 days or more, and the default hold is 60 days minus the grace period.
- Your app and server must grant access during grace and remove it in billing retry or account hold. StoreKit 2, Play Billing and the server notifications tell you which state a subscriber is in.
- Show the stores' own fix-your-payment prompts: StoreKit's Billing Problem message and Play's In-App Messaging API.
What a billing grace period does
A subscription that fails to renew because of a payment problem does not end straight away. Both stores move it into a recovery window and retry the charge. What differs is whether the subscriber keeps access while that happens:
| App Store | Google Play | |
|---|---|---|
| Total recovery window | Billing retry, up to 60 days | Grace period plus account hold, 60 days by default |
| Access kept during | Billing Grace Period, if you enable it | Grace period |
| Access removed during | Billing retry after any grace period | Account hold |
| Default | Off | On for every auto-renewing base plan |
| Renewal date if recovered in grace | Unchanged | Unchanged |
| Renewal date if recovered later | Moves to the recovery date | Moves to the recovery date |
Apple puts the benefit plainly: "There won't be any interruption to the subscriber's days of paid service or to your revenue if Apple recovers the subscription within the grace period." Without it, "the subscriber's days of paid service pause until Apple is able to collect payment." That matters beyond one month's revenue: Apple's subscriptions page explains that net revenue rises once a subscriber accrues a year of paid service, and a pause delays that.
There is a cost. If Apple cannot collect within the grace period, the subscriber had free access for those days: "Payment for the provided full service during the grace period would not be collected." For most apps that is a small price for keeping people who never chose to leave.

Most failed renewals are an expired or replaced card. A grace period gives the subscriber time to update it without losing anything. Photo: Thirdman / Pexels
How to enable Billing Grace Period on the App Store
Apple applies one setting to every subscription in the app; you cannot configure products individually. You need the Account Holder, Admin or App Manager role.
- In App Store Connect, open Apps and select your app.
- In the sidebar, click Subscriptions.
- In the Billing Grace Period section, click Set Up Billing Grace Period.
- Choose a duration: 3, 16 or 28 days.
- Choose the renewal types: All Renewals (including free trials and free offers converting to paid) or Only Paid to Paid Renewals.
- Choose the environment: Only Sandbox Environment first, then Production and Sandbox Environment once your app handles it.
- Click Confirm.
How the duration maps to plan length:
| Your setting | Weekly plans | Monthly and longer |
|---|---|---|
| 3 days | 3 days | 3 days |
| 16 days | 6 days | 16 days |
| 28 days | 6 days | 28 days |
A few rules from Apple's help page that catch people out:
- Changes can take up to 24 hours and apply only to upcoming renewals. People already in a grace period keep it.
- If you turn it off, keep serving subscribers who are already in one.
- It does not apply to monthly subscriptions with a 12-month commitment.
- The grace period is fixed for a subscriber when the billing error happens and "cannot be altered once assigned to a user."
- You can manage the setting through the App Store Connect API as well.
Which renewal types to choose is a business call. All Renewals gives a trial user who hits a declined card on their first charge a few more days; Only Paid to Paid avoids extending free access to people who have never paid. If your trial conversion is the step you are trying to protect, test both.
Enabling it in App Store Connect is only half the job. Apple says it directly: "Your app still needs to perform purchase validation and account for the subscriber's renewal status." A common question on Stack Overflow, "Do I need the server side to support iOS grace period?", has the same answer: yes, if your server decides who gets premium features.
How grace period and account hold work on Google Play
Google's model has two phases. From the Play Console help: "By default, this recovery period consists of a grace period, followed by an account hold period."
- Grace period: the subscriber keeps access. The
subscriptionStateisSUBSCRIPTION_STATE_IN_GRACE_PERIOD, Play extendsexpiryTimeas it goes, andqueryPurchasesAsync()still returns the purchase. You get an RTDN of typeSUBSCRIPTION_IN_GRACE_PERIOD. - Account hold: access should be blocked. The state is
SUBSCRIPTION_STATE_ON_HOLD,expiryTimeis in the past, andqueryPurchasesAsync()no longer returns the purchase. You getSUBSCRIPTION_ON_HOLD. - Recovery: when the user fixes their payment, you get
SUBSCRIPTION_RECOVERED, with the same purchase token. Recovery in grace keeps the renewal date; recovery from hold moves it to the recovery date. - No recovery: at the end of hold you get
SUBSCRIPTION_CANCELEDfollowed bySUBSCRIPTION_EXPIRED.
Two details matter for the numbers. Grace period is a setting on each auto-renewing base plan, and Google warns that "specifying a length less than the default value may reduce the number of subscriptions recovered." Account hold defaults to "60 days minus any grace period duration," and "the total of account hold and grace period durations must total 30 days or more." If you set a 0-day grace period, Play still waits at least a day, a "silent grace period" in which the subscription stays ACTIVE and no grace RTDN is sent.
To review the settings, open your subscription in Play Console, edit each auto-renewing base plan, and check the grace period and account hold lengths. If a plan was created with a short grace period to stop "free riders," compare its recovery rate with your other plans before keeping it.
What Google announced in September
On September 29, Google's post Driving growth on Google Play: the next era of subscriptions described a Dynamic Grace Period: Play uses "machine learning and heuristic models" to "tailor the grace period duration for individual subscribers following a payment decline," and adjusts account hold so the total recovery window stays the same, with no client code changes. The post says many of its features are "currently available or rolling out through our Early Access Program" without saying which, so treat it as coming rather than live for your app. The same post confirms the In-App Messaging API covered below is "available now to all developers." If you are also migrating billing code this quarter, our Google Play Billing Library 8 guide covers the version requirement.
Handle the states in your app
Your entitlement check should grant access in grace and deny it in billing retry or hold. With StoreKit 2, the subscription status gives you the state and the grace period end date:
import StoreKit
enum SubscriptionAccess {
case active
case gracePeriod(until: Date?) // payment failed, still entitled
case billingRetry // payment failed, not entitled
case none
}
func subscriptionAccess(groupID: String) async -> SubscriptionAccess {
guard let statuses = try? await Product.SubscriptionInfo.status(for: groupID) else {
return .none
}
var result = SubscriptionAccess.none
for status in statuses {
guard case .verified(let renewal) = status.renewalInfo else { continue }
switch status.state {
case .subscribed:
return .active
case .inGracePeriod:
result = .gracePeriod(until: renewal.gracePeriodExpirationDate)
case .inBillingRetryPeriod:
if case .none = result { result = .billingRetry }
default:
break
}
}
return result
}
Apple's RenewalState documentation confirms that subscribed and inGracePeriod are entitled to service, and inBillingRetryPeriod is not unless another status grants access, such as Family Sharing. That is why the loop checks every status before deciding.
In grace, keep the app fully working and show a calm banner: "We couldn't renew your subscription. Update your payment method to keep access." Do not lock features early; Apple asks you to "provide full service for the subscription throughout the grace period."
Let the stores ask for the new card
Both stores have a built-in prompt, and it converts better than an email because the fix is one tap away.
App Store. When a renewal fails, StoreKit shows a Billing Problem sheet on launch, with an Add Payment Method link. Apple's billingIssue documentation lists how often: every 24 hours on days 1 to 3, every 72 hours on days 4 to 16, every 96 hours on days 17 to 30 and every 120 hours on days 31 to 60. Apps built for iOS 16 or later can listen to StoreKit.Message.messages and delay the sheet until a calm moment, such as after onboarding, then show it with displayStoreKitMessage in SwiftUI or display(in:) in UIKit. Delay it, do not suppress it. Apple also documents a direct link to the payment page, https://apps.apple.com/account/billing, for your own banner.
Google Play. Call the In-App Messaging API each time the app opens. Google shows a snackbar with a link to fix the payment in Play:
val params = InAppMessageParams.newBuilder()
.addInAppMessageCategoryToShow(InAppMessageCategoryId.TRANSACTIONAL)
.build()
billingClient.showInAppMessages(activity, params) { result ->
if (result.responseCode == InAppMessageResponseCode.SUBSCRIPTION_STATUS_UPDATED) {
// Recovered: send result.purchaseToken to your server and refresh access.
refreshEntitlement(result.purchaseToken)
}
}
For your own banner, Google's subscriptions guide gives the subscription management link, https://play.google.com/store/account/subscriptions?sku=your-sub-product-id&package=your-app-package, where the user can update their payment method.

The client check is short. The part that usually goes wrong is the server, which keeps its own copy of who is entitled. Photo: Nemuel Sereti / Pexels
Keep your server in step
If your backend stores an entitlement flag, it has to follow the same states. Both stores send notifications when something changes: App Store Server Notifications V2 and Google Play real-time developer notifications (RTDN). Google is explicit that an RTDN only says something changed, so fetch the current state with purchases.subscriptionsv2.get before you update anything. On Apple, the App Store Server API's subscription status uses 1 active, 2 expired, 3 billing retry, 4 billing grace period and 5 revoked.
This small module maps both into one answer. It runs as is on Node.js 18 or later:
// entitlement.mjs
const APPLE_STATUS = { 1: 'active', 2: 'expired', 3: 'billing_retry', 4: 'grace', 5: 'revoked' };
// App Store Server Notifications V2 (notificationType, subtype) to state.
export function appleNotificationState(type, subtype) {
if (type === 'DID_FAIL_TO_RENEW') return subtype === 'GRACE_PERIOD' ? 'grace' : 'billing_retry';
if (type === 'GRACE_PERIOD_EXPIRED') return 'billing_retry';
if (type === 'DID_RENEW' || type === 'SUBSCRIBED') return 'active';
if (type === 'EXPIRED') return 'expired';
if (type === 'REFUND' || type === 'REVOKE') return 'revoked';
return null; // not an entitlement change: re-fetch the status if unsure
}
export const appleStatusState = (status) => APPLE_STATUS[status] ?? null;
// Google Play subscriptionsv2 resource to state.
export function playState(sub, now = Date.now()) {
const expiry = Math.max(0, ...(sub.lineItems ?? []).map((item) => Date.parse(item.expiryTime) || 0));
switch (sub.subscriptionState) {
case 'SUBSCRIPTION_STATE_ACTIVE': return 'active';
case 'SUBSCRIPTION_STATE_IN_GRACE_PERIOD': return 'grace';
case 'SUBSCRIPTION_STATE_ON_HOLD': return 'account_hold';
case 'SUBSCRIPTION_STATE_CANCELED': return expiry > now ? 'active' : 'expired'; // paid until expiry
default: return 'expired'; // pending, paused, expired
}
}
export const hasAccess = (state) => state === 'active' || state === 'grace';
// Quick check: node entitlement.mjs
if (import.meta.url === `file://${process.argv[1]}`) {
const rows = [
['Apple DID_FAIL_TO_RENEW/GRACE_PERIOD', appleNotificationState('DID_FAIL_TO_RENEW', 'GRACE_PERIOD')],
['Apple DID_FAIL_TO_RENEW', appleNotificationState('DID_FAIL_TO_RENEW')],
['Apple DID_RENEW/BILLING_RECOVERY', appleNotificationState('DID_RENEW', 'BILLING_RECOVERY')],
['Apple status 4', appleStatusState(4)],
['Play IN_GRACE_PERIOD', playState({ subscriptionState: 'SUBSCRIPTION_STATE_IN_GRACE_PERIOD' })],
['Play ON_HOLD', playState({ subscriptionState: 'SUBSCRIPTION_STATE_ON_HOLD' })],
];
for (const [event, state] of rows) console.log(event.padEnd(38), state.padEnd(14), hasAccess(state));
}
Running it prints:
Apple DID_FAIL_TO_RENEW/GRACE_PERIOD grace true
Apple DID_FAIL_TO_RENEW billing_retry false
Apple DID_RENEW/BILLING_RECOVERY active true
Apple status 4 grace true
Play IN_GRACE_PERIOD grace true
Play ON_HOLD account_hold false
Verify every Apple notification's signature before you trust it (Apple's App Store Server Library does this), and store the grace period end date (gracePeriodExpiresDate on Apple, the line item expiryTime on Google) so a missed notification cannot leave someone entitled forever.
Test it before you ship
App Store sandbox. From Apple's testing guide, on a device running iOS 16 or later:
- Enable Billing Grace Period for Only Sandbox Environment.
- Buy a subscription with a Sandbox Apple Account.
- Open Settings > Developer > Sandbox Account > Manage > Account Settings and turn off Allow Purchases & Renewals.
- Wait for the next accelerated renewal. It fails, and the subscription enters the grace period. Check that the app still unlocks everything and reads
gracePeriodExpirationDate. - Wait for the grace period to end. The subscription stays in billing retry, and access should end.
- Turn the setting back on to simulate the subscriber fixing their card, and confirm access returns.
Google Play. With a license tester account, Google's testing guide says to open Account > Subscriptions in the Play Store app and switch the test subscription's payment method to "Test instrument, always declines". You can also move a subscription between states with Play Billing Lab. Test timings are short: a grace period lasts about 5 minutes and account hold about 10.
Your billing grace period checklist
- App Store: Billing Grace Period enabled, with a duration and renewal type you chose on purpose.
- Google Play: every auto-renewing base plan reviewed for grace period and account hold length.
- App grants access in
subscribed/inGracePeriodandACTIVE/IN_GRACE_PERIOD, and removes it in billing retry and account hold. - Server listens for
DID_FAIL_TO_RENEW,GRACE_PERIOD_EXPIRED,DID_RENEWandEXPIRED, and for Play's grace, hold, recovered and expired RTDNs. - Server re-fetches status from the App Store Server API or
subscriptionsv2.getinstead of trusting a notification alone. - StoreKit Billing Problem sheet shown (delayed if needed, never suppressed), and Play's In-App Messaging API called on every launch.
- A friendly in-app banner during grace, linking to each store's payment page.
- Sandbox and license tester runs cover grace, retry or hold, recovery and expiry.
Recovered subscribers are the cheapest ones you will ever win. For the ones who did choose to leave, our guides to App Store offer codes and app retention rate cover win-back offers and measuring churn, and App Store subscription bundles covers the iOS 27 multiseat changes. Our Google Play service fees guide explains what a recovered renewal earns you after fees.
Bring in new subscribers, too
A grace period keeps the subscribers you have; your store listing and your promotion bring in the next ones. Our guides to App Store custom product pages and the App Store description cover the two parts of the listing that explain your plans before anyone subscribes. Outside the stores, you can put your app on the AppRanker leaderboard, a pay-to-rank board for App Store and Google Play apps where position is a plain sort on what each listing has paid and every row links straight to your store page.
FAQ
What is a billing grace period?
It is a window after a failed subscription renewal in which the subscriber keeps access while the store retries the payment. If the payment succeeds in that window, the subscription continues on its original renewal date and the developer is paid for the full period.
How long is the App Store billing grace period?
You choose 3, 16 or 28 days in App Store Connect. Weekly subscriptions are capped at 6 days, so a 16 or 28-day setting gives weekly plans 6 days. Apple's overall billing retry lasts up to 60 days, but access after the grace period ends is not expected.
Is billing grace period enabled by default?
Not on the App Store: you turn it on under Subscriptions in App Store Connect. On Google Play, every auto-renewing base plan has a grace period and account hold enabled by default, and you can change or disable them.
What is account hold on Google Play?
Account hold follows the grace period. The subscriber loses access, the purchase disappears from queryPurchasesAsync(), and Play keeps trying to collect payment. By default it lasts 60 days minus the grace period. If the user fixes their payment, you get a SUBSCRIPTION_RECOVERED notification and must restore access.
Do I need server-side changes to support a grace period?
If your server decides who gets paid features, yes. It must treat a subscription in grace as entitled and one in billing retry or account hold as not entitled. Apps that check access only on the device with StoreKit 2 or queryPurchasesAsync() get the right answer from the store.
Do I get paid for the grace period if the payment never goes through?
No. If Apple or Google cannot collect payment, the subscriber had those days for free. If payment is recovered within the grace period, you are paid for the full period and the renewal date does not change.
Image credits
- Cover, payment cards and a smartphone on a pink surface: photo by Nataliya Vaitkevich on Pexels, Pexels License. Cropped to 1200x630.
- Holding a payment card and a phone: photo by Thirdman on Pexels, Pexels License. Cropped.
- Code on a laptop in a dark room: photo by Nemuel Sereti on Pexels, Pexels License. Resized.


