What does the Google Play notification ONE_TIME_PRODUCT_CANCELED mean?

The Google Play notification ONE_TIME_PRODUCT_CANCELED means a customer cancelled a one-time purchase that was still pending.

Quick facts#

Notification oneTimeProductNotification.notificationType 2, ONE_TIME_PRODUCT_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 pending one-time product purchase has been canceled by the user.

What triggers it#

  • The customer started a purchase with a delayed payment method and cancelled before it finished.
  • It is about a pending purchase only. A refund of a completed purchase arrives as a voided purchase notification.
  • Like the purchase type, it is sent only when you enabled one-time product events.

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. Drop any pending record for that token and sku.
  3. Do not deliver the item.
  4. Do not confuse it with a refund: use the voided purchase notification for those.
  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","oneTimeProductNotification":{"version":"1.0","notificationType":2,"purchaseToken":"PURCHASE_TOKEN","sku":"coins_100"}})).toString("base64") } };

function decide(body) {
  const n = JSON.parse(Buffer.from(body.message.data, "base64").toString("utf8"));
  if (!(n.oneTimeProductNotification?.notificationType === 2)) return "ignore";
  return "drop the pending purchase, deliver nothing";
}

console.log(decide(body));

Run-checked: npm run check:snippets runs this snippet with Node and compares its output with deliver nothing (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 ignores it: the message is stored and answered 2xx with status ignored, and only type 1 (purchased) is applied. Refunds come through the voided purchase notification.

Source#

Checked: 2026-10-03

Edit this page on GitHub ↗ View as Markdown Last updated