signalapp/Signal-Server · error · BadRequestException
400 Bad Request (INVALID_ARGUMENTS)
Error message
400 Bad Request (INVALID_ARGUMENTS)
What it means
setSubscriptionLevel catches SubscriptionInvalidArgumentsException and returns a 400 with error type INVALID_ARGUMENTS, carrying the underlying exception message: the combination of arguments (level/currency/processor/template/payment method) is invalid for the subscription.
Solutions
- Read e.getMessage()/the INVALID_ARGUMENTS error text to see which argument is invalid and correct it
- Ensure the existing subscription's type matches the requested level (the subscriptionsAreSameType check)
- Create an appropriate payment method for the target processor before changing level
Example fix
// before — mismatched currency vs existing subscription
{ "level": 5, "currency": "gbp" }
// after
{ "level": 5, "currency": "usd" } Defensive patterns
Strategy: try-catch
Validate before calling
// ensure the new level's type matches the existing subscription
const sameType = existingSubscription.templateType === templateFor(level, currency, processor);
if (!sameType) throw new Error('Level change would switch subscription type'); Try / catch
try { await setSubscriptionLevel(level, currency); } catch (e) { if (e.errorType === 'INVALID_ARGUMENTS') { console.warn(e.message); await fixAndRetry(e); } else { throw e; } } Prevention
- Keep currency, level, and processor consistent with the existing subscription
- Read the INVALID_ARGUMENTS message to identify the offending field
When it happens
Trigger: Requesting a level change whose arguments don't fit the existing subscription (e.g. switching subscription type incompatibly, mismatched currency, missing required fields).
Common situations: Changing to a level backed by a different payment processor without creating a matching payment method; sending a currency not supported for that level.
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
- cannot create payment methods with payment type
- 400 Bad Request (UNSUPPORTED_LEVEL)
- 400 Bad Request
- Invalid length
- Invalid create call link credential request
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/044f7ab278fe01e5.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java:440
final CustomerAwareSubscriptionPaymentProcessor manager = getCustomerAwareProcessor(
processorCustomer.processor());
subscriptionManager.updateSubscriptionLevelForCustomer(subscriberCredentials, record, manager, level,
currency, idempotencyKey, subscriptionTemplateId, this::subscriptionsAreSameType);
return new SetSubscriptionLevelSuccessResponse(level);
} catch (SubscriptionInvalidLevelException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)
.entity(new SubscriptionController.SetSubscriptionLevelErrorResponse(List.of(
new SubscriptionController.SetSubscriptionLevelErrorResponse.Error(
SubscriptionController.SetSubscriptionLevelErrorResponse.Error.Type.UNSUPPORTED_LEVEL,
null))))
.build());
} catch (SubscriptionPaymentRequiresActionException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)
.entity(new SetSubscriptionLevelErrorResponse(List.of(new SetSubscriptionLevelErrorResponse.Error(
SetSubscriptionLevelErrorResponse.Error.Type.PAYMENT_REQUIRES_ACTION, null))))
.build());
} catch (SubscriptionInvalidArgumentsException e) {
throw new BadRequestException(Response.status(Response.Status.BAD_REQUEST)
.entity(new SetSubscriptionLevelErrorResponse(List.of(new SetSubscriptionLevelErrorResponse.Error(
SetSubscriptionLevelErrorResponse.Error.Type.INVALID_ARGUMENTS, e.getMessage()))))
.build());
}
}
public boolean subscriptionsAreSameType(long level1, long level2) {
return subscriptionConfiguration.getSubscriptionLevel(level1).type()
== subscriptionConfiguration.getSubscriptionLevel(level2).type();
}
@POST
@Path("/{subscriberId}/appstore/{originalTransactionId}")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@Operation(summary = "Set app store subscription", description = """
Set an originalTransactionId that represents an IAP subscription made with the app store.
View on GitHub (pinned to 100ab61c82)