RTDN Reference
Phase 0: Intent
Tell the user: "I will look up the RTDN notification type you asked about and recommend handling."
Phase 1: Locate
Map the user's question to one of the tables in Phase 2:
- Integer code given (for example
notificationType: 5): determine which notification family the user is in.- If the payload wraps the code in
subscriptionNotification, use the Subscription Notification Types table. - If the payload wraps the code in
oneTimeProductNotification, use the One Time Product Notification Types table. - If the payload has
voidedPurchaseNotification, use the Voided Purchase Notification section. - If the payload has
testNotification, use the Test Notification section.
- If the payload wraps the code in
- Event name given (for example
SUBSCRIPTION_ON_HOLD): search the Type column in the Subscription or One Time Product table. - Symptom given (for example "user lost access after failed payment"): match the trigger phrase in the Trigger column. Account hold and grace period map to codes 5 and 6 respectively in the subscription table.
- Unassigned code (14, 15, 16, 21): report that the code is reserved and not emitted.
After locating the row, jump to Phase 3 for the handling guidance for that family.
Phase 2: Reference tables
RTDN Message Structure
Every RTDN message arrives as a Cloud Pub/Sub message with a base64 encoded JSON payload in the data field. The decoded JSON has this structure:
{
"version": "1.0",
"packageName": "com.example.app",
"eventTimeMillis": "1234567890123",
"subscriptionNotification": { },
"oneTimeProductNotification": { },
"voidedPurchaseNotification": { },
"testNotification": { }
}
Only one notification field is present per message.
Subscription Notification Types
| Code | Type | Trigger | Subscription State After | Required Action |
|---|---|---|---|---|
| 1 | SUBSCRIPTION_RECOVERED |
Payment recovered from grace period, account hold, or pause resumes | ACTIVE |
Grant access. Call subscriptionsv2.get to confirm state. |
| 2 | SUBSCRIPTION_RENEWED |
Active subscription successfully renewed | ACTIVE |
Extend entitlement. Update expiryTime in your database. |
| 3 | SUBSCRIPTION_CANCELED |
Subscription canceled (user, developer, or system) | CANCELED |
Check canceledStateContext for reason. User retains access until expiryTime. |
| 4 | SUBSCRIPTION_PURCHASED |
New subscription purchased | ACTIVE |
Verify purchase. Grant access. Acknowledge within 3 days. |
| 5 | SUBSCRIPTION_ON_HOLD |
Grace period expired, entered account hold | ON_HOLD |
Revoke access. Notify user to fix payment method. |
| 6 | SUBSCRIPTION_IN_GRACE_PERIOD |
Renewal payment failed, entered grace period | IN_GRACE_PERIOD |
Retain access. Notify user about payment issue. Show in app message. |
| 7 | SUBSCRIPTION_RESTARTED |
User restored canceled subscription from Play Store before expiration | ACTIVE |
Grant access. Same purchase token. |
| 8 | SUBSCRIPTION_PRICE_CHANGE_CONFIRMED |
(Deprecated) User confirmed a price change | No state change | Deprecated. Use type 19 instead. |
| 9 | SUBSCRIPTION_DEFERRED |
Subscription billing date extended via subscriptionsv2.defer |
ACTIVE |
Update expiryTime in your database. Extend access. |
| 10 | SUBSCRIPTION_PAUSED |
Subscription paused (effective at end of current period) | PAUSED |
Revoke access when pause begins. |
| 11 | SUBSCRIPTION_PAUSE_SCHEDULE_CHANGED |
Pause schedule modified (pause set, changed, or removed) | Varies | Call API to check current pause schedule. |
| 12 | SUBSCRIPTION_REVOKED |
Subscription revoked (refund, developer revocation) | EXPIRED |
Immediately revoke access. |
| 13 | SUBSCRIPTION_EXPIRED |
Subscription expired after cancellation or account hold failure | EXPIRED |
Revoke access. Clean up entitlement records. |
| 17 | SUBSCRIPTION_ITEMS_CHANGED |
Items in subscription bundle changed (add ons) | Varies | Call API to get updated line items. |
| 18 | SUBSCRIPTION_CANCELLATION_SCHEDULED |
Installment subscription cancellation scheduled at end of commitment | ACTIVE (until commitment ends) |
Note the scheduled cancellation. User retains access through commitment period. |
| 19 | SUBSCRIPTION_PRICE_CHANGE_UPDATED |
Price change details updated for existing subscribers | Varies | Call API to get updated price change information. |
| 20 | SUBSCRIPTION_PENDING_PURCHASE_CANCELED |
Pending subscription purchase was canceled | EXPIRED |
Do not grant access. Clean up any pending records. |
| 22 | SUBSCRIPTION_PRICE_STEP_UP_CONSENT_UPDATED |
Price step up consent period begun or user provided consent (South Korea) | Varies | Call API to check consent status. |
Notification type codes 14, 15, 16, and 21 are not assigned. Do not expect to receive these values.
Subscription notification payload:
{
"version": "1.0",
"notificationType": 4,
"purchaseToken": "purchase-token-string",
"subscriptionId": "premium_monthly"
}
One Time Product Notification Types
| Code | Type | Trigger | Required Action |
|---|---|---|---|
| 1 | ONE_TIME_PRODUCT_PURCHASED |
One time product purchase completed | Verify purchase. Grant access. Acknowledge or consume. |
| 2 | ONE_TIME_PRODUCT_CANCELED |
Pending one time product purchase was canceled | Do not grant access. Clean up pending records. |
One time product notification payload:
{
"version": "1.0",
"notificationType": 1,
"purchaseToken": "purchase-token-string",
"sku": "remove_ads"
}
One time product notifications include the sku field (the product ID); subscription notifications include subscriptionId.
Voided Purchase Notification
Sent when a purchase is voided due to a refund, chargeback, or revocation.
{
"purchaseToken": "purchase-token-string",
"orderId": "GPA.1234-5678-9012-34567",
"productType": 1,
"refundType": 1
}
| Field | Values |
|---|---|
productType |
1 = Subscription, 2 = One time product |
refundType |
1 = Full refund, 2 = Quantity based refund |
Test Notification
{
"version": "1.0"
}
Test notifications have only a version field inside the testNotification object.
Phase 3: Recommended handling
Subscription notifications
For every subscription notification:
- Decode the base64
datafield from the Pub/Sub message. - Parse the
DeveloperNotificationand readsubscriptionNotification.notificationType. - Extract
purchaseTokenandsubscriptionId. - Call
purchases.subscriptionsv2.geton the Google Play Developer API with the purchase token. - Apply the action from the Required Action column in the table, using the API response as the source of truth.
- Acknowledge the Pub/Sub message.
Code-specific notes:
- Codes
4(purchase) and1(recovered) require acknowledging the purchase within 3 days or Google refunds it. - Code
3(canceled) is not an immediate revocation. Grant access untillineItems[].expiryTime. - Code
5(on hold) is the revoke-now signal after a failed renewal. Code6(grace period) is the keep-access signal. - Code
12(revoked) requires immediate access revocation regardless ofexpiryTime. - Codes
11,17,19,22have aVariesstate; the notification flags that something changed and the API holds the new value. - Code
8is deprecated. New integrations should handle code19instead.
One time product notifications
- Read
oneTimeProductNotification.notificationTypeandpurchaseToken. - Call
purchases.products.geton the Google Play Developer API. - For code
1, verify the purchase state, grant the entitlement, then acknowledge (non-consumable) or consume (consumable) within 3 days. - For code
2, discard any pending record you created when the user began checkout.
Voided purchase notifications
- Revoke access immediately for the
purchaseToken. - Call the Voided Purchases API (
purchases.voidedpurchases.list) for full details, includingvoidedReasonandvoidedSource. - Use
productTypeto route to your subscription or one time product tables. - Use
refundTypeto decide whether to revoke the full entitlement or only an adjusted quantity.
Test notifications
Acknowledge the Pub/Sub message without processing it as a real event. Log receipt so you can confirm your integration is wired up when you trigger a test from Play Console.
Handler pattern (applies to every RTDN)
- Receive the Pub/Sub message.
- Decode the base64
datafield. - Parse the JSON
DeveloperNotification. - Identify which notification field is present.
- Extract the
purchaseToken. - Call the Google Play Developer API for current state.
- Update your database from the API response, not from the notification alone.
- Acknowledge the Pub/Sub message.
Never rely solely on the RTDN payload for business logic. RTDNs signal that something changed. The API tells you the current state.