signalapp/Signal-Server · error · BadRequestException
The PAYPAL payment type must use…
Error message
The PAYPAL payment type must use create_payment_method/paypal
What it means
SubscriptionController.createPaymentMethod throws BadRequestException for the PAYPAL payment type because PayPal must be created through the dedicated create_payment_method/paypal endpoint (processed by Braintree), not the generic switch that routes CARD/SEPA/IDEAL to Stripe.
Solutions
- Call POST /create_payment_method/paypal instead of the generic create_payment_method
- If Stripe-compatible types are intended, switch the payment type to CARD, SEPA_DEBIT, or IDEAL
Example fix
// before
POST /v1/subscription/create_payment_method body: {"paymentMethodType":"PAYPAL", ...}
// after
POST /v1/subscription/create_payment_method/paypal body: {"paymentMethodType":"PAYPAL", ...} Defensive patterns
Strategy: validation
Validate before calling
if (paymentMethodType === 'PAYPAL') {
return createPaypalPaymentMethod(args); // use /create_payment_method/paypal
} Type guard
const isPaypal = (t) => t === 'PAYPAL';
Prevention
- Branch PAYPAL requests to the dedicated paypal endpoint before calling the generic one
- Keep endpoint routing in one helper so PAYPAL can't leak to the Stripe path
When it happens
Trigger: Calling the generic POST create_payment_method endpoint with paymentMethodType PAYPAL instead of the paypal-specific endpoint.
Common situations: Clients constructing a single unified payment-method creation call for all processors; missing the paypal sub-route in the client SDK.
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
- Invalid payment method
- Operation cannot be performed with the
- cannot use play billing for one-time donations
- 400 Bad Request (INVALID_ARGUMENTS)
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/b6827d3a9c38885f.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java:291
@Parameter(description="A base64-encoded donation permit retrieved from POST /v1/donation/permit")
@HeaderParam(HeaderUtils.DONATION_PERMIT)
final Optional<DonationPermitHeader> donationPermitHeader,
@PathParam("subscriberId") String subscriberId,
@QueryParam("type") @DefaultValue("CARD") PaymentMethod paymentMethodType,
@HeaderParam(HttpHeaders.USER_AGENT) @Nullable final String userAgentString) throws SubscriptionException {
SubscriberCredentials subscriberCredentials =
SubscriberCredentials.process(authenticatedAccount, subscriberId, clock);
final CustomerAwareSubscriptionPaymentProcessor customerAwareSubscriptionPaymentProcessor = switch (paymentMethodType) {
// Today, we always choose stripe to process non-paypal payment types, however we could use braintree to process
// other types (like CARD) in the future.
case CARD, SEPA_DEBIT, IDEAL -> stripeManager;
case GOOGLE_PLAY_BILLING, APPLE_APP_STORE ->
throw new BadRequestException("cannot create payment methods with payment type " + paymentMethodType);
case PAYPAL -> throw new BadRequestException("The PAYPAL payment type must use create_payment_method/paypal");
case UNKNOWN -> throw new BadRequestException("Invalid payment method");
};
SubscriptionsUtil.recordDonationPermitPresent(donationPermitHeader.isPresent(), "createPaymentMethod", userAgentString);
final boolean spendSuccessful = donationPermitHeader.map(
permitHeader -> {
try {
return SubscriptionsUtil.verifyAndSpendDonationPermit(permitHeader.permit(), donationPermitsManager, clock);
} catch (VerificationFailedException e) {
return false;
}
})
.orElse(false);
if (!spendSuccessful) {
throw new WebApplicationException(Response.Status.UNAUTHORIZED);
}
View on GitHub (pinned to 100ab61c82)