Guide · Chapter 1 of 4

How Google Play reports subscription state

Play tells your backend about a subscription in two different ways, and they do not carry the same information. Knowing which one moved an entitlement explains most of what you will see in it.

Two inputs, one entitlement

The first input is a subscription query. When Tierux verifies or refreshes a purchase, it calls Google's purchases.subscriptionsv2.get with the purchase token and reads back the subscription's subscriptionState, the first line item's expiryTime and productId, and, on a plan change, a linkedPurchaseToken. This is the authoritative snapshot.

The second input is a Real-Time Developer Notification (RTDN). Play publishes it to Pub/Sub when something changes. It carries the package name, the purchase token, the subscription id, and a numeric notificationType — and nothing about the user. Tierux resolves the user by hashing the token and looking up the purchase it already verified, then resolves the entitlement through your product mapping. If the chain breaks — an unrecognized package, a token with no purchase record, an unmapped product — the notification is acknowledged without a write. The delivery mechanics — Pub/Sub authentication, idempotency, and ordering — are covered in chapter 4 of the verification guide.

Both inputs write the same entitlement record, with the same six statuses described in the entitlement state machine. What differs is how much each one knows.

From a subscription query

A fresh query maps Play's subscriptionState onto a Tierux status, and grants access only for the two states where Play says the subscriber is entitled:

Play subscriptionStateTierux statusAccess
SUBSCRIPTION_STATE_ACTIVEactiveOn
SUBSCRIPTION_STATE_IN_GRACE_PERIODgrace_periodOn
SUBSCRIPTION_STATE_ON_HOLDaccount_holdOff
SUBSCRIPTION_STATE_PAUSEDaccount_holdOff
SUBSCRIPTION_STATE_CANCELEDcancelledOff
SUBSCRIPTION_STATE_EXPIREDexpiredOff

✓ access on · ✕ access off

Other values — SUBSCRIPTION_STATE_UNSPECIFIED or a pending purchase, for example — become unknown and are treated as not valid. Two further checks apply on top of the table. If Play's expiryTime is already in the past, access is off and the status is rewritten to expired. And if Play said "active" but the check above turned access off, the status becomes unknown rather than staying active, so the record does not go on claiming a live subscription that did not hold up.

Note the SUBSCRIPTION_STATE_CANCELED row. A query of a canceled subscription turns access off, even though Play still considers the subscriber entitled until expiryTime. The cancellation notification below does not; it leaves access as it was.

From a notification

A notification maps each notificationType to an entitlement effect. "Unchanged" means the current access flag is preserved:

TypeNotificationTierux statusAccessTimeline event
1SUBSCRIPTION_RECOVEREDactiveOn—
2SUBSCRIPTION_RENEWEDactiveOnrenewal
3SUBSCRIPTION_CANCELEDcancelledUnchangedcancellation_pending
4SUBSCRIPTION_PURCHASEDactiveOn—
5SUBSCRIPTION_ON_HOLDaccount_holdOffbilling_retry
6SUBSCRIPTION_IN_GRACE_PERIODgrace_periodOngrace_period
7SUBSCRIPTION_RESTARTEDactiveOn—
10SUBSCRIPTION_PAUSEDunknownOff—
12SUBSCRIPTION_REVOKEDcancelledOffrefund
13SUBSCRIPTION_EXPIREDexpiredOffexpired

✓ access on · ~ access unchanged · ✕ access off

Types 8 (price change confirmed), 9 (deferred), and 11 (pause schedule changed) are informational: they are acknowledged and write nothing. A voidedPurchaseNotification, which is a separate message shape rather than a subscription type, is covered in chapter 4.

The four types that re-query Play

For types 1, 2, 4, and 7, the table row is a starting point, not the final answer. Tierux calls subscriptionsv2.get again and overwrites status, access, and expiresAt with the fresh result from the query table above. A "renewed" notification for a subscription Play now reports as expired is therefore written as expired, and no renewal is added to the timeline. If that re-query fails, the RTDN endpoint answers 503 so Pub/Sub redelivers the message later; reprocessing is safe because of the message-id and event-time guards.

The other types — cancel, hold, grace, pause, revoke, expire — are applied from the notification alone. They update status and access but leave expiresAt as last stored. That detail matters in chapter 2.

Where the two disagree

Because the two inputs carry different information, the same Play state can land as a different Tierux status depending on which one arrived last:

The practical rule is the one the state machine chapter already gives: gate on active, and treat status as the explanation — except during grace, where active can read false until a refresh (see chapter 2). The billing timeline is the place to see the sequence of events that produced it.

What reaches your systems

Each applied notification with a timeline event in the table above is appended to the subscriber's billing timeline in the dashboard, and, if you have configured an outbound webhook, posted to it as a signed POST with the event type in the X-Tierux-Event header. That delivery is best-effort and at most once — a failed POST is logged and not retried — so treat it as a prompt to read the entitlement, not as the record itself. See the webhooks docs for the contract.

The App Store has its own notification vocabulary and its own mapping onto the same six statuses; that path is implemented with limitations. See the platform support matrix before relying on parity.

Related reading

Glossary: RTDN

The notification channel this chapter maps, in one paragraph.

Blog: RTDN vs polling

Why lifecycle state should arrive by push rather than by periodic re-verification.

Docs: RTDN setup

Pointing Play Console at the shared Pub/Sub topic.

Guide: The entitlement state machine

The six statuses and why active and status are separate fields.

Platform support matrix

RTDN on Google Play, App Store Server Notifications on Apple — per-feature status.

← Back to the subscription lifecycle guide

Play state, normalized

Free tier — unlimited apps, 1 paywall. No credit card, no revenue share.

Start free