What does the Google Play notification SUBSCRIPTION_CANCELED mean?

The Google Play notification SUBSCRIPTION_CANCELED means a subscription was cancelled, but the customer keeps access until the paid period ends.

Quick facts#

Notification subscriptionNotification.notificationType 3, SUBSCRIPTION_CANCELED
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 was cancelled, voluntarily or involuntarily. For a voluntary cancellation, it is sent when the user cancels.

What triggers it#

  • The customer cancelled in the Play subscriptions center, you cancelled with purchases.subscriptionsv2.cancel, or the subscription was cancelled after account hold ran out.
  • The subscription resource then has subscriptionState SUBSCRIPTION_STATE_CANCELED, and expiryTime says when access ends. If that time is already past, for example after a cancel during account hold, access ends now.
  • canceledStateContext says who cancelled and userInitiatedCancellation can say why.

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. Keep access until expiryTime. Google warns that removing access a customer is still entitled to violates its subscriptions policy.
  3. Show a message with the end date and a way to resubscribe.
  4. Read canceledStateContext to choose your win-back message.
  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":3,"purchaseToken":"PURCHASE_TOKEN"}})).toString("base64") } };

function decide(body) {
  const n = JSON.parse(Buffer.from(body.message.data, "base64").toString("utf8"));
  if (!(n.subscriptionNotification?.notificationType === 3)) return "ignore";
  return "keep access until expiryTime, show the end date";
}

console.log(decide(body));

Run-checked: npm run check:snippets runs this snippet with Node and compares its output with keep access until expiryTime (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 unsubscribed and sends CANCELLATION. The cancel_reason is DEVELOPER_INITIATED when Google says you cancelled it, BILLING_ERROR or PRICE_INCREASE when the system did, and UNSUBSCRIBE otherwise. A cancellation caused by an upgrade or downgrade is not a customer cancellation and is skipped. The EXPIRATION event comes later.

Source#

Checked: 2026-10-03

Edit this page on GitHub ↗ View as Markdown Last updated