signalapp/Signal-Server · error · SubscriptionInvalidArgumentsException
transaction is not a consumable one-time purchase
Error message
transaction is not a consumable one-time purchase
What it means
claimOneTimePurchase only accepts App Store transactions whose decoded payload type is CONSUMABLE (a one-time purchase). If the JWS transaction's type field is anything else (e.g. AUTO_RENEWABLE, NON_CONSUMABLE), Signal throws SubscriptionInvalidArgumentsException because the claim path is designed exclusively for consumable one-time purchases like one-time donations.
Solutions
- Verify the client sends the transactionId of the consumable one-time purchase, not a subscription's originalTransactionId.
- Check the product in App Store Connect is still configured as Consumable; re-purchase if the product type changed.
- Decode the JWS transaction client-side and assert type == CONSUMABLE before calling the claim API.
- If you need to redeem a subscription, use the subscription flow (lookupAndValidateTransaction) instead of claimOneTimePurchase.
Example fix
// before
client.claimOneTimePurchase(subscriptionTransactionId);
// after: client-side check first
if (decodedTransaction.getType() == Type.CONSUMABLE) {
client.claimOneTimePurchase(decodedTransaction.getTransactionId());
} Defensive patterns
Strategy: validation
Validate before calling
// client-side pre-check
if (decodedJws.getType() != Type.CONSUMABLE) { throw new Error("only consumable one-time purchases can be claimed"); } Type guard
function isConsumable(tx) { return tx?.getType?.() === 'CONSUMABLE'; } Try / catch
try {
api.claimOneTimePurchase(purchaseId);
} catch (SubscriptionInvalidArgumentsException e) {
showError("This purchase is not a one-time purchase.");
} Prevention
- Decode the JWS transaction client-side and assert type == CONSUMABLE before submitting
- Keep App Store Connect product types stable; never convert consumables to subscriptions
- Route subscription transactions to the subscription flow, not the one-time-purchase claim flow
When it happens
Trigger: Passing a purchaseId/originalTransactionId whose App Store JWS transaction type is AUTO_RENEWABLE_SUBSCRIPTION or NON_CONSUMABLE into the one-time-purchase claim endpoint.
Common situations: Client accidentally sends a subscription transactionId instead of the consumable purchase's transactionId; a product was reconfigured in App Store Connect from consumable to non-consumable or subscription.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- transactionId did not include a paid subscription or the…
- unknown payment provider:
- purchase was for an unexpected product
- must cancel subscription with storekit before deleting
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/e6501a34d3c4fa5e.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/AppleAppStoreManager.java:169
final PaymentTime paymentTime = PaymentTime.periodEnds(Instant.ofEpochMilli(tx.transaction().getExpiresDate()));
return new ReceiptItem(itemId, paymentTime, getLevel(tx.transaction()).getValue());
}
@Override
public Optional<PaymentDetails> claimOneTimePurchase(final String transactionId)
throws RateLimitExceededException, SubscriptionInvalidArgumentsException {
final Optional<JWSTransactionDecodedPayload> maybeTransaction =
appleAppStoreClient.lookupTransaction(transactionId, Tags.of(LOOKUP_TYPE_TAG, "one_time"));
if (maybeTransaction.isEmpty()) {
return Optional.empty();
}
final JWSTransactionDecodedPayload transaction = maybeTransaction.get();
if (transaction.getType() != Type.CONSUMABLE) {
throw new SubscriptionInvalidArgumentsException("transaction is not a consumable one-time purchase");
}
final PaymentStatus paymentStatus = transaction.getRevocationDate() == null
? PaymentStatus.SUCCEEDED
: PaymentStatus.FAILED;
return Optional.of(new PaymentDetails(
Objects.requireNonNull(transaction.getTransactionId()),
getLevel(transaction),
paymentStatus,
transaction.getPurchaseDate() != null ? Instant.ofEpochMilli(transaction.getPurchaseDate()) : null,
null));
}
private AppleAppStoreDecodedTransaction lookupSubscription(final String originalTransactionId, final Tags tags)
throws RateLimitExceededException, SubscriptionNotFoundException {
try {
return lookupAndValidateTransaction(originalTransactionId, tags);View on GitHub (pinned to 100ab61c82)