signalapp/Signal-Server · error · SubscriptionProcessorConflictException
existing processor does not match
Error message
existing processor does not match
What it means
addPaymentMethodToCustomer throws SubscriptionProcessorConflictException("existing processor does not match") when the subscription record's stored ProcessorCustomer was created with a different payment provider than the one handling the current request. A subscription is bound to exactly one processor (Stripe, Braintree, Google Play, App Store); you cannot add payment methods from a second processor to it.
Solutions
- Route the request to the processor that matches record.getProcessorCustomer().processor() instead of the configured one.
- If the user genuinely wants to switch processors, cancel the existing subscription first, then create a new subscription with the new processor.
- Verify server config/env so donation endpoints map to the same processor the client used at subscription creation.
Example fix
// before PaymentProvider provider = PaymentProvider.BRAINTREE; // hardcoded manager.addPaymentMethodToCustomer(creds, new BraintreeManager(...), setupFn); // after PaymentProvider provider = record.getProcessorCustomer().orElseThrow().processor(); manager.addPaymentMethodToCustomer(creds, getProcessor(provider), setupFn);
Defensive patterns
Strategy: validation
Validate before calling
Subscriptions.Record record = manager.getSubscriber(creds);
PaymentProvider existing = record.getProcessorCustomer().map(ProcessorCustomer::processor).orElse(null);
if (existing != null && existing != paymentProcessor.getProvider()) {
throw new IllegalStateException("subscription already uses " + existing);
} Type guard
boolean processorMatches(Subscriptions.Record r, PaymentProvider p) {
return r.getProcessorCustomer().map(ProcessorCustomer::processor).map(p::equals).orElse(true);
} Try / catch
try {
manager.addPaymentMethodToCustomer(creds, processor, setupFn);
} catch (SubscriptionProcessorConflictException e) {
// redirect user to the processor that owns the subscription or offer cancel-and-recreate
} Prevention
- Check the record's processor before presenting payment-method UI to the user
- Support only one processor per subscription lifecycle; switch = cancel + recreate
- Keep processor routing configuration consistent between client and server environments
When it happens
Trigger: Calling addPaymentMethodToCustomer with a subscriptionPaymentProcessor whose getProvider() differs from processorCustomer.processor() stored on the record (e.g. subscription created via Stripe, now adding a Braintree payment method).
Common situations: Client switches payment provider mid-subscription without first cancelling/migrating; wrong processor manager selected by configuration for an endpoint; user pays via app store then tries web card payment on the same subscription.
Understand the failure class
Background: Conflicting config options: "cannot be used together" — configuration validation errors across open-source libraries — this error's family across 162 libraries.
Related errors
- cannot create payment methods with payment type
- The PAYPAL payment type must use…
- Invalid payment method
- Operation cannot be performed with the
- 400 Bad Request (UNSUPPORTED_LEVEL)
AI-assisted analysis of signalapp/Signal-Server@100ab61c82 (2026-09-09).
Data as JSON: /api/errors/4bea3c76c9ec2e1d.
Report an issue: GitHub.
Appendix: source
Thrown at service/src/main/java/org/whispersystems/textsecuregcm/storage/SubscriptionManager.java:274
final SubscriberCredentials subscriberCredentials,
final T subscriptionPaymentProcessor,
final ClientPlatform clientPlatform,
final ThrowingBiFunction<T, String, R, E> paymentSetupFunction)
throws SubscriptionForbiddenException, SubscriptionNotFoundException, SubscriptionProcessorConflictException, E {
Subscriptions.Record record = this.getSubscriber(subscriberCredentials);
if (record.getProcessorCustomer().isEmpty()) {
final ProcessorCustomer pc = subscriptionPaymentProcessor
.createCustomer(subscriberCredentials.subscriberUser(), clientPlatform);
record = subscriptions.setProcessorAndCustomerId(record,
new ProcessorCustomer(pc.customerId(), subscriptionPaymentProcessor.getProvider()),
Instant.now());
}
final ProcessorCustomer processorCustomer = record.getProcessorCustomer()
.orElseThrow(() -> new UncheckedIOException(new IOException("processor must now exist")));
if (processorCustomer.processor() != subscriptionPaymentProcessor.getProvider()) {
throw new SubscriptionProcessorConflictException("existing processor does not match");
}
return paymentSetupFunction.apply(subscriptionPaymentProcessor, processorCustomer.customerId());
}
public interface LevelTransitionValidator {
/**
* Check is a level update is valid
*
* @param oldLevel The current level of the subscription
* @param newLevel The proposed updated level of the subscription
* @return true if the subscription can be changed from oldLevel to newLevel, otherwise false
*/
boolean isTransitionValid(long oldLevel, long newLevel);
}
/**
* Update the subscription level in the payment processor and update the table.View on GitHub (pinned to 100ab61c82)