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

  1. Read e.getMessage()/the INVALID_ARGUMENTS error text to see which argument is invalid and correct it
  2. Ensure the existing subscription's type matches the requested level (the subscriptionsAreSameType check)
  3. 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

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


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)