signalapp/Signal-Server · error · SubscriptionPaymentRequiredException
Cannot acknowledge purchase for subscription in state
Error message
Cannot acknowledge purchase for subscription in state ${subscription.getSubscriptionState()} What it means
GooglePlayBillingManager.validateToken acknowledges a Play purchase only when the subscription state is ACTIVE, IN_GRACE_PERIOD, or CANCELED (canceled-but-not-yet-expired still grants entitlement). Any other state (e.g. PENDING, IN_GRACE until expiry, PAUSED, ON_HOLD, EXPIRED) raises SubscriptionPaymentRequiredException because the purchase cannot be acknowledged or entitled in that state.
Solutions
- Ask the user to fix their payment method in Google Play (the purchase is likely PENDING or ON_HOLD), then resubmit the token.
- If the subscription expired, have the user re-subscribe and submit the new purchase token instead of the old one.
- Handle SubscriptionPaymentRequiredException in the client by prompting the user to restore/repurchase rather than retrying with the same token.
- Query the purchase's state in Play Developer API before calling to pre-check eligibility and give a better error.
Example fix
// before: resubmitting an expired token
client.validateToken(oldExpiredPurchaseToken);
// after: check state and repurchase if needed
if (purchaseState != ACTIVE && purchaseState != IN_GRACE_PERIOD && purchaseState != CANCELED) {
promptUserToResubscribe();
} else {
client.validateToken(purchase.getPurchaseToken());
} Defensive patterns
Strategy: validation
Validate before calling
SubscriptionState state = AcknowledgementState/subscription state from Play API;
if (state != ACTIVE && state != IN_GRACE_PERIOD && state != CANCELED) {
promptUserToFixPaymentOrResubscribe();
return;
} Type guard
function isAcknowledgableState(s) { return ['ACTIVE','IN_GRACE_PERIOD','CANCELED'].includes(s); } Try / catch
try {
api.validateToken(purchaseToken);
} catch (SubscriptionPaymentRequiredException e) {
showPaymentIssueScreen(); // direct user to Play Store payment settings
} Prevention
- Check subscription state via Play Developer API before submitting the token
- Handle PENDING/ON_HOLD by prompting the user to fix their payment method
- For EXPIRED subscriptions, require a fresh purchase rather than resubmitting the old token
When it happens
Trigger: User submits their Play purchase token while the subscription is in PENDING (payment method pending), PAUSED, ON_HOLD (billing declined repeatedly), or fully EXPIRED state.
Common situations: Expired subscriber trying to re-link a stale token; purchase stuck in PENDING because the user's payment method requires action in Play Store; subscription paused/hold due to failed renewals.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/1f2811de9c7f9edc.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/GooglePlayBillingManager.java:176
* user an entitlement
*/
public ValidatedToken validateToken(String purchaseToken)
throws RateLimitExceededException, SubscriptionNotFoundException, SubscriptionPaymentRequiredException {
final SubscriptionPurchaseV2 subscription = lookupSubscription(purchaseToken);
final SubscriptionState state = SubscriptionState
.fromString(subscription.getSubscriptionState())
.orElse(SubscriptionState.UNSPECIFIED);
Metrics.counter(VALIDATE_COUNTER_NAME, subscriptionTags(subscription)).increment();
// We only accept tokens in a state where the user may be entitled to their purchase. This is true even in the
// CANCELLED state. For example, a user may subscribe for 1 month, then immediately cancel (disabling auto-renew)
// and then submit their token. In this case they should still be able to retrieve their entitlement.
// See https://developer.android.com/google/play/billing/integrate#life
if (state != SubscriptionState.ACTIVE
&& state != SubscriptionState.IN_GRACE_PERIOD
&& state != SubscriptionState.CANCELED) {
throw new SubscriptionPaymentRequiredException(
"Cannot acknowledge purchase for subscription in state " + subscription.getSubscriptionState());
}
final AcknowledgementState acknowledgementState = AcknowledgementState
.fromString(subscription.getAcknowledgementState())
.orElse(AcknowledgementState.UNSPECIFIED);
final SubscriptionPurchaseLineItem purchase = getLineItem(subscription);
final ReceiptLevel level = productIdToLevel(purchase.getProductId());
return new ValidatedToken(level.getValue(), purchase.getProductId(), purchaseToken, requiresAcknowledgement(subscription));
}
/**
* Cancel the subscription. Cancellation stops auto-renewal, but does not refund the user nor cut off access to their
* entitlement until their current period expires.
*View on GitHub (pinned to 100ab61c82)