What does the Google Play notification VOIDED_PURCHASE mean?
A Google Play voidedPurchaseNotification means a purchase was voided, usually by a refund or a chargeback, so what it granted should be taken back.
Quick facts#
| Notification | voidedPurchaseNotification |
| 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 purchase has been voided. The message gives the purchase token, the order ID, the product type (subscription or one-time) and the refund type (full or quantity-based partial). |
What triggers it#
- The customer was refunded, charged back, or you revoked the purchase.
productType1 is a subscription and 2 a one-time product.refundType1 is a full refund and 2 a quantity-based partial refund of a multi-quantity purchase.- For subscriptions, a new order ID is created for each renewal, so the order ID says which period was voided.
What your server should do#
- Find the purchase by
purchaseTokenandorderIdand revoke what that order granted. - For a partial refund, adjust the quantity instead of revoking everything.
refundableQuantityin the products API gives what is left. - For more data, use Google's Voided Purchases API, a pull model over a time range.
- Answer 2xx once you have handled the message, and skip a
messageIdyou have seen: Google recommends checking the uniqueness of message IDs so a redelivery does not make a second API call.
Example#
// 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","voidedPurchaseNotification":{"purchaseToken":"PURCHASE_TOKEN","orderId":"GPA.1234-5678-9012-34567","productType":1,"refundType":1}})).toString("base64") } };
function decide(body) {
const n = JSON.parse(Buffer.from(body.message.data, "base64").toString("utf8"));
if (!(n.voidedPurchaseNotification)) return "ignore";
const v = n.voidedPurchaseNotification;
if (v.refundType === 2) return "reduce the quantity, keep the purchase";
return v.productType === 1 ? "revoke the subscription period of this order" : "revoke the one-time item";
}
console.log(decide(body));Run-checked: npm run check:snippets runs this snippet with Node and compares its output with revoke the subscription period (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. For a subscription RevenueDot marks the voided period refunded (only if the order is the current one) and sends CANCELLATION with cancel_reason: CUSTOMER_SUPPORT and a negative price. A one-time purchase is marked refunded the same way. A partial quantity refund (refundType 2) of a one-time product is ignored. A daily scan of Google's voided purchases catches refunds whose message never arrived.
Related#
- What does the Google Play notification SUBSCRIPTION_REVOKED mean?
- What does the Google Play notification ONE_TIME_PRODUCT_PURCHASED mean?
- What does the Google Play notification SUBSCRIPTION_CANCELED mean?
- Which webhook events does RevenueDot send?
- Set up Google Play notifications in RevenueDot
Source#
- Google: Real-time developer notifications reference guide
- RevenueDot server: stores/google/notifications.ts (how the message is received)
- RevenueDot server: stores/google/sync.ts (how the purchase is re-read and applied)
- RevenueDot core: events.ts (how a change becomes a webhook event)
- RevenueDot server: services/tick.ts (the scheduled EXPIRATION)
- RevenueDot server: stores/google/notifications.ts (how the message is received)
Checked: 2026-10-03