Subscription lifecycle

Subscription purchases can go through several different states throughout their lifecycle, depending on many factors including auto-renewal behavior, payment decline situations, and developer management actions.

Handle lifecycle for auto-renewing subscriptions

When a user's subscription state changes, your backend server receives a SubscriptionNotification message

Figure 1. Lifecycle states and transition events for auto-renewing subscription purchases.

To update the state in your backend, call the purchases.subscriptionsv2.get API with the purchase token included in the notification. This endpoint provides the latest subscription state given a purchase token and is considered the source of truth for subscription management.

The purchase token is valid from subscription signup until 60 days after expiration. After this date, the purchase token is no longer valid to use to call the Google Play Developer API.

New auto-renewing subscription purchases

When a user purchases a subscription, a SubscriptionNotification message with type SUBSCRIPTION_PURCHASED is sent to your RTDN client. Whether you receive this notification or you register a new purchase in-app through PurchasesUpdatedListener or manually fetching purchases in your app's onResume() method, you should process the new purchase in your secure backend. To do this, follow these steps:

  1. Query the purchases.subscriptionsv2.get endpoint to get a subscription resource that contains the latest subscription state.
  2. Make sure that the value of the subscriptionState field is SUBSCRIPTION_STATE_ACTIVE.
  3. Verify the purchase.
  4. Give the user access to the content. The user account associated with the purchase can be identified with the ExternalAccountIdentifiers object from the subscription resource if identifiers were set at purchase time using setObfuscatedAccountId and setObfuscatedProfileId.

The Play Billing Library also includes a method to acknowledge a subscription, acknowledgePurchase(), and a method to check acknowledgement status, isAcknowledged(). However, we recommend that you handle purchase processing in your backend for better security.

The subscription resource for new purchases looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  "startTime": "2022-04-22T18:39:58.270Z",
  "regionCode": "US",
  "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
  "latestOrderId": "GPA.3333-4137-0319-36762",
  "acknowledgementState": "ACKNOWLEDGEMENT_STATE_PENDING", // need to acknowledge new purchases
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": next_renewal_date,
      "autoRenewingPlan": {
        "autoRenewEnabled": true
      }
    }
  ],
}

Subscription renewals

A SUBSCRIPTION_RENEWED notification is sent when an auto-renewing subscription renews. Make sure that the user is still entitled to the subscription and then update the subscription state with the new expiryTime provided in the subscription resource returned from the Google Play Developer API. The subscription resource looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  "startTime": "2022-04-22T18:39:58.270Z",
  "regionCode": "US",
  "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
  "latestOrderId": "GPA.3333-4137-0319-36762",
  "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": next_renewal_date,
      "autoRenewingPlan": {
        "autoRenewEnabled": true
      }
    }
  ]
}

You don't need to acknowledge subscription renewals.

Grace period

If there are payment issues with a subscription renewal, Google notifies the user and periodically attempts to renew the subscription for some time before the subscription expires. This recovery period can consist of a grace period followed by an account hold period. During a grace period, the user should still have access to their subscription entitlement.

The queryPurchasesAsync() method continues to return purchases that are in the grace period. If your app relies solely on queryPurchasesAsync to check whether a user is entitled to a subscription, then your app should automatically handle grace periods, because these subscriptions are shown as active through the Play Billing Library.

Synchronizing subscription state with your backend allows you to be more aware of payment declines and gives you more context as you try to reduce involuntary churn. Listen for SubscriptionNotification messages with type SUBSCRIPTION_IN_GRACE_PERIOD to be notified when the user enters a grace period. While the user is in a grace period, the subscription resource contains autoRenewEnabled = true. Google Play dynamically extends the expiryTime value until the grace period has expired because entitlement should last until the user cancels or the grace period has lasted for its maximum length. The value of the subscriptionState field during this period is SUBSCRIPTION_STATE_IN_GRACE_PERIOD. The subscription resource looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_IN_GRACE_PERIOD",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": timestamp_in_future,
      "autoRenewingPlan": {
        "autoRenewEnabled": true
      }
    }
  ],
}

Play informs users that are in a grace period that their payment was declined and prompts them to fix their payment method issues in the Play Store. When a user enters a grace period, you should also encourage the user to fix their payment method in case the failure was involuntary. A straightforward way to do this is to use the In-App Messaging API. If you call this API when the user opens your app, they are shown a Play message in a temporary snackbar informing the user that their payment has been declined. This message also includes a deep link for the user to fix their payment method on Google Play.

As soon as the user fixes their payment method, the subscription renews with its original renewal date, and you can handle the renewal as described in Renewals.

If the user does not fix their payment method during the grace period, the subscription enters account hold, and they lose entitlement.

Grace period access and recovery

Figure 2 shows a timeline for a subscription that enters into a grace period and then recovers when the user fixes their payment method. After the grace period ends, the user should lose subscription benefits and go into account hold.

Figure 2. Timeline for a subscription that enters a grace period and recovers before it ends.

It is important to remember the following points:

  • During a grace period, the user should retain access to subscription benefits.
  • When a subscription recovers during a grace period, the renewal date does not reset.
  • If you increase the grace period—for example, from 7 days to 14 days—users who are in a grace period get extended access to subscription benefits.
  • If you decrease the grace period, users who are far enough into the old grace period to exceed the new grace period have their subscription benefits revoked immediately. For example, if you decrease the grace period from 14 days to 7 days, users who are in days 8-14 of the old grace period have their subscription benefits revoked immediately.

Silent grace period

You can set a grace period of 0 days, but Play will wait a minimum of 1 day to ensure sufficient time for payment retries. This silent grace period offers a safety net for payment processing. During this 24‑hour period the subscription remains in the ACTIVE state.

The best way for you to stay in sync with subscription state changes is to listen and react to the real-time developer notifications (RTDN). Call the purchases.subscriptionsv2.get() method at the RTDN time instead of the expiry time to get a more accurate status of the subscription.

Depending on the subscription status after the 24‑hour silent grace period, you should receive one of the following notifications:

  • SUBSCRIPTION_ON_HOLD (if enabled)
  • SUBSCRIPTION_CANCELED (if canceled)
  • SUBSCRIPTION_EXPIRED (if expired)
  • SUBSCRIPTION_RENEWED (if successfully renewed)

You can also call the subscriptionV2.get() method at any point after the 24‑hour silent grace period to get the latest status of the subscription.

Account hold

If there are payment issues with a subscription renewal, after any grace period has ended, an account hold period begins. When a subscription enters account hold, you should block access to the subscription entitlement.

During account hold, you should continue to handle any cancellations, restorations, or repurchases of your subscriptions as needed, because it's possible for the user to make these changes while the subscription is on hold.

RTDNs notify you when the user enters the account hold period, so you can inform them as soon as possible of why their access to the subscription was suspended. A straightforward way to do this is to use the In-App Messaging API. Calling this API when your user opens the app will show the user a message in a temporary snackbar informing them that their payment has been declined. This message also includes a deep link for the user to fix their payment method on Google Play.

If your users can access subscription content outside of your app, they might discover that they have lost access on different surfaces. You might want to send a push notification or an email to the user to let them know that their subscription is no longer active due to payment decline.

The subscription is not returned by the queryPurchasesAsync() method during account hold, so if your app relies on this method to display existing purchases, you should support account hold by default.

With real-time developer notifications, you receive a SubscriptionNotification message with type SUBSCRIPTION_ON_HOLD when a subscription enters account hold. Call the purchases.subscriptionsv2.get method from your secure backend server to retrieve the new subscription information. During account hold the expiryTime field of the subscription resource is set to a past timestamp, and the subscriptionState field is set to SUBSCRIPTION_STATE_ON_HOLD:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_ON_HOLD",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": timestamp_in_past,
      ...
    }
  ],
}

To restore access, users must fix their payment method. Play informs users in account hold of their payment decline, and you should also encourage them to fix their payment method.

After the user fixes their payment method, the subscription returns to an active state, and you must then restore access to the subscribed content. In this case, the purchase token is the same as it was before the account hold started because the same purchase is recovering, and you receive an RTDN with type SUBSCRIPTION_RECOVERED.

After recovery, the Play Billing Library returns the subscription again through the queryPurchasesAsync() method. If you use this method to determine whether a user is entitled to a subscription, then your app should automatically handle the subscription recovering from account hold.

Listen for a SubscriptionNotification message with type SUBSCRIPTION_RECOVERED to be notified when a subscription is recovered and the user should regain access. If you query for a subscription after receiving this notification, the expiryTime field is set to a timestamp in the future and the subscriptionState field is set to SUBSCRIPTION_STATE_ACTIVE again:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": next_renewal_date,
      ...
    }
  ],
}

If the user does not fix their payment method before the end of the account hold period, you instead receive an RTDN with type SUBSCRIPTION_CANCELED. For instructions on handling a cancellation, see Cancellations. When you query for a subscription that was canceled in this way, the returned expiryTime field is set to a past timestamp:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_CANCELED",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": timestamp_in_past,
      ...
    }
  ],
}

Immediately after you are notified of the cancellation during account hold, you will also receive an RTDN with type SUBSCRIPTION_EXPIRED because the user is out of paid entitlement and the subscription has churned with the cancellation. You can handle this expiration the way you normally would.

The user can regain access by repurchasing the same subscription plan or any other plan that you offer through the app during their account hold period from their original purchase. In that case, a new purchase token is issued and the new value is returned as part of a SUBSCRIPTION_PURCHASED event that represents this new instance.

Account hold access and recovery

Figure 3 shows a timeline for a subscription that enters into account hold and then recovers when the user fixes their payment method.

Figure 3. Timeline for a subscription that enters an account hold and recovers before it ends.

Similar to the previous example, Figure 4 shows a timeline for a subscription that first enters into a grace period before entering account hold, and then recovers while on hold.

Figure 4. Timeline for a subscription that enters a grace period, then enters account hold, and finally recovers before the account hold ends.

It is important to remember the following points:

  • Before a subscription enters into account hold, Google Play makes additional attempts to charge the payment method for up to 48 hours. The user retains subscription benefits during this period. After this retry period has elapsed, the subscription then enters into account hold, and the user should lose access to subscription benefits.
  • The subscription enters into account hold directly when the subscription resumes from a paused state with a failed form of payment.
  • When a subscription recovers from account hold, the renewal date resets.

Expirations

Once a subscription expires, the user should lose access to the subscription. A SubscriptionNotification message with type SUBSCRIPTION_EXPIRED is sent in that case. When you receive this notification, query the Google Play Developer API to get the latest subscription resource. After you confirm that the subscriptionState is SUBSCRIPTION_STATE_EXPIRED, remove the entitlement and register the purchase state as invalid in your backend. The subscription resource looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_EXPIRED",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": expiration_time_in_past,
      ...
    }
  ],
}

Cancellations

A user can voluntarily cancel a subscription from the Play subscriptions center or have their subscription automatically canceled if they don't recover after being in account hold. Developers can also trigger a cancellation with purchases.subscriptions.cancel When a subscription is canceled, the user retains access to the content until the end of the current billing cycle. When the billing cycle ends, access should be revoked.

Canceling a subscription triggers a SUBSCRIPTION_CANCELED notification. When you receive this notification, the subscription resource returned from the Google Play Developer API has the subscriptionState field set to SUBSCRIPTION_STATE_CANCELED, and the expiryTime field contains the date when the user should lose access to the subscription. If that date is in the past, then the user should lose entitlement immediately. This could happen, for example, if a user cancels a subscription while on account hold due to a payment decline.

The subscription resource for a canceled purchase looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_CANCELED",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": expiration_time,
      ...
    }
  ],
}

You can look at the canceledStateContext field in the subscription resource to learn why the subscription was canceled (for example, whether the subscription was canceled by the user, by the system, or by you). If the subscription was canceled by the user, you can look at the userInitiatedCancellation field to learn why the user canceled the subscription. This can help inform communication strategies.

When a subscription is canceled but has not yet expired, it is still returned from queryPurchasesAsync(). You might want to display a message in your app informing the user that their subscription was canceled and giving them the date of expiration.

Revocations

A subscription can be revoked for a variety of reasons, including your backend revoking the subscription by using purchases.subscriptions.revoke or the purchase being charged back. In this situation, revoke the user's entitlement immediately. A SubscriptionNotification message with type SUBSCRIPTION_REVOKED is sent when this occurs. When you receive this notification, the subscription resource returned from the Google Play Developer API has the subscriptionState field set to SUBSCRIPTION_STATE_EXPIRED.

The subscription resource for a revoked purchase looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_EXPIRED",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": expiration_time,
      ...
    }
  ]
}

Deferred subscriptions

There are a variety of reasons why you might want to extend a user's entitlement. For example, you might want to offer users free access as a special promotion, such as giving one week free for purchasing a movie or providing free access to customers as a gesture of goodwill. You can use the purchases.subscriptions.defer method from the Play Developer API to advance the next bililng date for an auto-renewing subscription. When you do this, a SubscriptionNotification message with type SUBSCRIPTION_DEFERRED is sent. During the deferral period, the user is subscribed to your content with full access but is not charged. The subscription renewal date is updated to reflect the new date.

For prepaid plans, you can use the defer billing API to defer the expiration time.

The subscription resource for a deferred subscription looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": timestamp_in_future,
      ...
    }
  ],
}

Paused subscriptions

You can reduce voluntary churn by enabling users to pause their subscription. When you enable the pause feature, users can choose to pause their subscription for a period of time between one week and three months, depending on the recurring period.

A subscription pause takes effect only after the current billing period ends. While the subscription is paused, the user doesn't have access to the subscription, and they don't pay the renewal price. At the end of the pause period, the subscription resumes and Google attempts to renew the subscription. If the resume is successful, the subscription becomes active again. If the resume fails due to a payment issue, the user enters the account hold state as shown in figures 5 and 6:

Figure 5. A user pauses and then resumes their subscription.
Figure 6. A user pauses their subscription and then enters account hold.

A user can also choose to manually resume a subscription at any time during the pause period, as shown in figure 6. When a user resumes manually, the billing date changes to the manual resume date.

When a user's subscription is paused, the Play Billing Library doesn't return the subscription through the queryPurchasesAsync() method. If the subscription is resumed, the queryPurchasesAsync() method returns it again.

Listen for RTDNs to be aware of when a user pauses their subscription. These notifications also allow you to notify your users in your app that they have paused their subscription and don't have access to it. You should also provide a way for the user to manually resume their subscription at any time by using a deep link to Google Play.

A SubscriptionNotification message with type SUBSCRIPTION_PAUSE_SCHEDULE_CHANGED is sent when your user initiates a pause of their subscription. At this time, the user should keep access to their subscription until the next renewal date, and the subscription resource contains autoRenewEnabled = true. The value of the subscriptionState field is SUBSCRIPTION_STATE_ACTIVE at this point.

A SubscriptionNotification message with type SUBSCRIPTION_PAUSED is sent when the pause goes into effect. When this happens, the user should lose access to their subscription, and the subscription resource contains autoRenewEnabled = true, and the subscriptionState field is set to SUBSCRIPTION_STATE_PAUSED. You can see when the subscription is expected to renew again by checking the PausedStateContext object.

A SubscriptionNotification message with type SUBSCRIPTION_RENEWED is sent if the subscription is resumed either automatically at the end of the pause period or if the user chose to manually resume the subscription. This should be handled as described in Renewals.

A SubscriptionNotification message with type SUBSCRIPTION_ON_HOLD is sent if there was a payment failure while trying to resume the subscription after pause. This should be handled as described in Account hold.

Resubscribe

For auto-renewing subscription base plans, the Google Play Store may display a Resubscribe button. This button allows users to regain access to a subscription. It may not appear for various reasons, for instance when a subscription expired a long time ago.

Figure 7. Account > Subscriptions section of the Google Play Store app showing a canceled subscription with a Resubscribe button.

Although the button is always labeled Resubscribe, its functionality depends upon the subscription state.

While a subscription is canceled but not yet expired, the user is still subscribed and receiving subscription benefits. If the user taps Resubscribe, the cancelation is effectively undone, and the subscription continues to renew. This action is known as restore in Play developer documentation and APIs.

After an auto-renewing subscription has expired, you can allow users to purchase the same subscription base plan. This action is known resubscribe in Play developer documentation and APIs. You can configure this option for each base plan in the Play Console or using the API.

Restore prior to expiration

If your app relies solely on the queryPurchasesAsync() method to determine whether a user is entitled to a subscription, then your app should automatically handle restorations because the queryPurchasesAsync() method continues to return canceled purchases before their expiration dates. A restored subscription continues to renew as if it were not canceled.

If your app synchronizes subscription state with a backend, you should listen for a SubscriptionNotification message with the type SUBSCRIPTION_RESTARTED. After you receive this RTDN, your app can respond to the notification, record that the subscription is now set to renew, and stop displaying restoration messages in your app. The subscription resource looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": next_renewal_date
      ...
    }
  ],
}

Resubscribe after expiration

If an auto-renewing base plan is configured using the Google Play Console or API to allow Resubscribe, users can re-purchase an expired subscription in the Google Play Store.

These are new purchases. Google Play issues a brand new purchase token, and your backend receives an RTDN with type SUBSCRIPTION_PURCHASED. The purchase status for this type of out-of-app purchase does not include a linkedPurchaseToken associated with the original purchase in that case, because the original subscription expired completely. These are new purchases that your backend must process and acknowledge like any other purchase.

Upgrades, downgrades, and resubscribe

When a user upgrades, downgrades, or signs up after cancellation from your app before the subscription expires, the old subscription is invalidated and a new subscription is created with a new purchase token.

In addition, the subscription resource returned from the Google Play Developer API contains a linkedPurchaseToken field that indicates the old purchase from which the user upgraded, downgraded, or resubscribed. You can use the purchase token in that field to look up the old subscription and identify the existing user account so that you can associate the new purchase with the same account.

Before offering upgrade, downgrade, or resubscribe options to a user in your app, you must acknowledge the existing subscription. Any plan change or resubscribe is blocked if the existing subscription is still pending acknowledgement.

If the user successfully purchases the upgrade, downgrade, or resubscribe, this is a new purchase which you must acknowledge. The recommended way to do this is to use the Google Play Developer API. The subscription resource looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  ...
  "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
  "linkedPurchaseToken": old_purchase_token,
  ...
  "lineItems": [
    {
      "productId": "sub_variant_plan01",
      "expiryTime": next_renewal_date,
      "autoRenewingPlan": {
        "autoRenewEnabled": true
      }
    }
  ],
}

Price changes

See the price change best practices guide to learn about changing auto-renewing subscription prices and notifying users when appropriate.

When price changes are applied to existing subscribers as opt-in, you will received an RTDN if the user takes action to confirm or reject the new price.

Handle user confirmation of an opt-in price change

When a user accepts your subscription price increase, you receive a SubscriptionNotification message with type SUBSCRIPTION_PRICE_CHANGED_CONFIRMED. With an opt-out price decrease, or when the subscription price increase renews, you receive a SubscriptionNotification message with type SUBSCRIPTION_RENEWED. Treat this notification like any other renewal.

Handle cases where an opt-in price increase is not accepted

If a user hasn't accepted your opt-in price increase before they need to renew at the higher price, they are automatically unsubscribed and you receive a SubscriptionNotification message with type SUBSCRIPTION_CANCELED. Handle this event as described in Cancellations.

Users can also cancel their subscriptions for an opt-out price increase following the same mechanism.

Handle lifecycle for prepaid plans

As with auto-renewing subscriptions, you must acknowledge prepaid plans after each new purchase. In the case of prepaid plans, you must fully process both the initial purchase and any top-ups, because the user has to go through the purchase flow every time.

Due to the potential for short prepaid plan durations, it's important to acknowledge the purchase as soon as possible. Prepaid plans with a duration of one week or longer must be acknowledged within 3 days. Prepaid plans with a duration shorter than one week must be acknowledged within half of the plan duration. For example, developers have 1.5 days to acknowledge purchase of a three-day prepaid plan.

Figure 8. Lifecycle states and transition events for subscription purchases.

A SubscriptionNotification message with type SUBSCRIPTION_PURCHASED is sent to your RTDN client whenever a prepaid plan subscription is purchased, including every top-up. Call the purchases.subscriptionsv2.get method to check for the latest prepaid plan subscription state.

A new purchase token is issued for top-up purchases, and you receive the previous purchase token in the linkedPurchaseToken field as part of the new subscription purchase state.

The subscription resource for a prepaid plan purchase looks similar to the following example:

{
  "kind": "androidpublisher#subscriptionPurchaseV2",
  "startTime": "2022-04-22T18:39:58.270Z",
  "regionCode": "US",
  "subscriptionState": "SUBSCRIPTION_STATE_ACTIVE",
  "latestOrderId": "GPA.3333-4137-0319-36762",
  "acknowledgementState": "ACKNOWLEDGEMENT_STATE_ACKNOWLEDGED",
  "lineItems": [
    {
      "productId": "prepaid_plan01",
      "expiryTime": expiry_date,
      "prepaidPlan": {
        "allowExtendAfterTime": timestamp_after_which_topups_are_allowed
      }
    }
  ]
}

You can see when the entitlement ends in the expiryTime field. Top-up purchases increase the entitlement time by accumulating it. That means that if the user tops up before their original entitlement ends, the new time is added on top of their previous expiration date.

You might want to display a message in your app informing the user that their prepaid subscriptions can be extended with a top-up. To know when a user will be able to top-up, check the allowExtendAfterTime field in the subscription resource.

Prepaid plans don't auto-renew, so they can't be canceled. If a user wants to cancel a prepaid plan, they can let it reach its expiration date.