What does the Google Play notification SUBSCRIPTION_EXPIRED mean?

The Google Play notification SUBSCRIPTION_EXPIRED means a subscription has run out and the customer should lose access.

Quick facts#

Notification subscriptionNotification.notificationType 13, SUBSCRIPTION_EXPIRED
Where Google Play real-time developer notifications (Pub/Sub, DeveloperNotification)
Sent by Google Play, as a Cloud Pub/Sub push to your endpoint
What Google says A subscription has expired.

What triggers it#

  • The paid period ended after a cancellation, or account hold ran out.
  • The subscription resource has subscriptionState SUBSCRIPTION_STATE_EXPIRED and expiryTime in the past.
  • A revoked subscription also reads as expired.

What your server should do#

  1. Do not trust the message for the state. Google says a notification only tells you the purchase state changed; call the Google Play Developer API (purchases.subscriptionsv2.get) with the purchase token to get the full state.
  2. Confirm subscriptionState is SUBSCRIPTION_STATE_EXPIRED, then remove the entitlement.
  3. Mark the purchase as no longer valid in your backend.
  4. Keep the customer's data, since they can resubscribe, which is a new purchase.
  5. Answer 2xx once you have handled the message, and skip a messageId you have seen: Google recommends checking the uniqueness of message IDs so a redelivery does not make a second API call.

Example#

JavaScript
// A Pub/Sub push body: the notification is base64 JSON in message.data.
const body = { message: { messageId: "136969346945", data: Buffer.from(JSON.stringify({"version":"1.0","packageName":"com.example.app","eventTimeMillis":"1790800914000","subscriptionNotification":{"version":"1.0","notificationType":13,"purchaseToken":"PURCHASE_TOKEN"}})).toString("base64") } };

function decide(body) {
  const n = JSON.parse(Buffer.from(body.message.data, "base64").toString("utf8"));
  if (!(n.subscriptionNotification?.notificationType === 13)) return "ignore";
  return "confirm the state, remove the entitlement";
}

console.log(decide(body));

Run-checked: npm run check:snippets runs this snippet with Node and compares its output with remove the entitlement (checked 2026-10-03).

How RevenueDot handles it#

RevenueDot answers every handled message with 2xx and every temporary failure with 5xx so Pub/Sub redelivers it. For subscription types it re-reads the subscription from Google with purchases.subscriptionsv2.get and applies that state, and it acknowledges a new purchase if the app has not. A purchase RevenueDot has not seen is applied only when the app's Track new purchases from server-to-server notifications setting is on. RevenueDot stores the purchase as ended and as unsubscribed (a CANCELLATION if none had been sent). The EXPIRATION event is recorded once by the scheduler when access, grace included, has ended, with the reason from the stored state.

Source#

Checked: 2026-10-03

Edit this page on GitHub ↗ View as Markdown Last updated