{"record":{"id":"b8e5b70d70ab38ab","repo":"signalapp/Signal-Server","slug":"login-purchases-are-not-enabled-b8e5b7","errorCode":null,"errorMessage":"login purchases are not enabled","messagePattern":"login purchases are not enabled","errorType":"http","errorClass":"BadRequestException","httpStatus":400,"severity":"warning","filePath":"service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java","lineNumber":334,"sourceCode":"\n    final AccountIdentityResponse identityResponse = new AccountIdentityResponseBuilder(account)\n        // If there was an existing account, return whether it could have had something in the storage service\n        .storageCapable(existingAccount\n            .map(a -> a.hasCapability(DeviceCapability.STORAGE))\n            .orElse(false))\n        .build();\n\n    return new AccountCreationResponse(identityResponse, existingAccount.isPresent());\n  }\n\n  private AccountCreationResponse registerAccountWithoutNumber(\n      final String password,\n      final RegistrationRequest registrationRequest,\n      final String userAgent,\n      final String signalAgent) {\n\n    if (!dynamicConfigurationManager.getConfiguration().getLoginPurchaseConfiguration().enabled()) {\n      throw new BadRequestException(\"login purchases are not enabled\");\n    }\n\n    registrationRequest.accountAttributes().recoveryPassword()\n        .filter(ArrayUtils::isNotEmpty)\n        .orElseThrow(() -> new WebApplicationException(\"Account recovery password is required\", 422));\n\n    final ReceiptCredentialPresentation receiptCredentialPresentation;\n    try {\n      receiptCredentialPresentation = receiptCredentialPresentationFactory\n          .build(registrationRequest.receiptCredentialPresentation());\n    } catch (InvalidInputException _) {\n      throw new BadRequestException(\"Invalid receipt credential presentation\");\n    }\n    try {\n      serverZkReceiptOperations.verifyReceiptCredentialPresentation(receiptCredentialPresentation);\n    } catch (VerificationFailedException _) {\n      throw new NotAuthorizedException(\"Receipt credential presentation verification failed\");\n    }","sourceCodeStart":316,"sourceCodeEnd":352,"githubUrl":"https://github.com/signalapp/Signal-Server/blob/100ab61c82627582c867d19e1c0561ba2781e927/service/src/main/java/org/whispersystems/textsecuregcm/controllers/RegistrationController.java#L316-L352","documentation":"registerAccountWithoutNumber (called from the /v1/registration flow in RegistrationController) rejects login-purchase registrations when the feature is switched off. The controller checks DynamicConfigurationManager's LoginPurchaseConfiguration.enabled() and throws a BadRequestException (HTTP 400) immediately if the server has not enabled the feature. It is a server-side feature gate, not a client error.","triggerScenarios":"A client POSTs to the registration endpoint with a receipt credential / login-purchase flow while the server operator's dynamic configuration has loginPurchaseConfiguration.enabled() set to false (or the config field is absent/default-disabled).","commonSituations":"Self-hosted or staging Signal servers where the login-purchase feature flag was never enabled; environments where the dynamic configuration YAML lacks the loginPurchase section (so defaults to disabled); production operators disabling the feature without coordinating with client releases that rely on it.","solutions":["Enable the feature in the server's dynamic configuration: set loginPurchaseConfiguration.enabled() to true and allow the config to propagate to the server.","Verify the deployed dynamic configuration actually contains the loginPurchaseConfiguration block and that the server picked it up (check config file and reload).","If you are a client developer, treat HTTP 400 'login purchases are not enabled' as feature-disabled and fall back to the normal (non-purchase) registration path.","Confirm you are targeting the intended server deployment; a staging server with the flag off will reject requests meant for production."],"exampleFix":"// server config (before)\nloginPurchase:\n  enabled: false\n// after\nloginPurchase:\n  enabled: true","handlingStrategy":"fallback","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n  registerWithoutNumber(request);\n} catch (BadRequestException e) {\n  if (\"login purchases are not enabled\".equals(e.getMessage())) {\n    fallBackToStandardRegistration(request); // feature-off path\n  } else throw e;\n}","preventionTips":["Query a feature/capability flag from the server before offering the login-purchase UI.","Keep server dynamic configuration and client releases coordinated.","Monitor for this 400 in staging to catch missing config blocks early."],"tags":["feature-flag","registration","bad-request","server-config"],"backgroundTag":"feature-not-enabled","analyzedSha":"100ab61c82627582c867d19e1c0561ba2781e927","analyzedAt":"2026-09-09T13:29:47.883Z","contentChangedAt":"2026-09-09T13:29:47.883Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}