diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java
index 801ad0071ac4aff2e04368784716bfe316a66515..5a15c0fa6d6f3f68245958d4907d47ac82848b51 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java
@@ -8,7 +8,9 @@ package org.whispersystems.textsecuregcm.controllers;
 import com.fasterxml.jackson.annotation.JsonInclude;
 import com.fasterxml.jackson.annotation.JsonInclude.Include;
 import com.google.common.annotations.VisibleForTesting;
+import com.google.common.net.HttpHeaders;
 import com.stripe.exception.StripeException;
+import com.vdurmont.semver4j.Semver;
 import io.dropwizard.auth.Auth;
 import io.micrometer.core.instrument.Metrics;
 import io.micrometer.core.instrument.Tag;
@@ -33,6 +35,7 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.CompletionException;
 import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
 import javax.crypto.Mac;
 import javax.crypto.spec.SecretKeySpec;
 import javax.validation.Valid;
@@ -60,7 +63,6 @@ import javax.ws.rs.QueryParam;
 import javax.ws.rs.WebApplicationException;
 import javax.ws.rs.container.ContainerRequestContext;
 import javax.ws.rs.core.Context;
-import javax.ws.rs.core.HttpHeaders;
 import javax.ws.rs.core.MediaType;
 import javax.ws.rs.core.Response;
 import javax.ws.rs.core.Response.Status;
@@ -94,6 +96,10 @@ import org.whispersystems.textsecuregcm.subscriptions.SubscriptionCurrencyUtil;
 import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessor;
 import org.whispersystems.textsecuregcm.subscriptions.SubscriptionProcessorManager;
 import org.whispersystems.textsecuregcm.util.ExactlySize;
+import org.whispersystems.textsecuregcm.util.ua.ClientPlatform;
+import org.whispersystems.textsecuregcm.util.ua.UnrecognizedUserAgentException;
+import org.whispersystems.textsecuregcm.util.ua.UserAgent;
+import org.whispersystems.textsecuregcm.util.ua.UserAgentUtil;
 
 @Path("/v1/subscription")
 @io.swagger.v3.oas.annotations.tags.Tag(name = "Subscriptions")
@@ -111,14 +117,13 @@ public class SubscriptionController {
   private final IssuedReceiptsManager issuedReceiptsManager;
   private final BadgeTranslator badgeTranslator;
   private final LevelTranslator levelTranslator;
-  private final Map<String, CurrencyConfiguration> currencyConfiguration;
-
   private static final String INVALID_ACCEPT_LANGUAGE_COUNTER_NAME = MetricsUtil.name(SubscriptionController.class,
       "invalidAcceptLanguage");
   private static final String RECEIPT_ISSUED_COUNTER_NAME = MetricsUtil.name(SubscriptionController.class, "receiptIssued");
   private static final String PROCESSOR_TAG_NAME = "processor";
   private static final String TYPE_TAG_NAME = "type";
   private static final String EURO_CURRENCY_CODE = "EUR";
+  private static final Semver LAST_PROBLEMATIC_IOS_VERSION = new Semver("6.44.0");
 
   public SubscriptionController(
       @Nonnull Clock clock,
@@ -141,16 +146,10 @@ public class SubscriptionController {
     this.issuedReceiptsManager = Objects.requireNonNull(issuedReceiptsManager);
     this.badgeTranslator = Objects.requireNonNull(badgeTranslator);
     this.levelTranslator = Objects.requireNonNull(levelTranslator);
-
-    this.currencyConfiguration = buildCurrencyConfiguration(this.oneTimeDonationConfiguration,
-        this.subscriptionConfiguration, List.of(stripeManager, braintreeManager));
   }
 
-  private static Map<String, CurrencyConfiguration> buildCurrencyConfiguration(
-      OneTimeDonationConfiguration oneTimeDonationConfiguration,
-      SubscriptionConfiguration subscriptionConfiguration,
-      List<SubscriptionProcessorManager> subscriptionProcessorManagers) {
-
+  private Map<String, CurrencyConfiguration> buildCurrencyConfiguration(@Nullable final UserAgent userAgent) {
+    final List<SubscriptionProcessorManager> subscriptionProcessorManagers = List.of(stripeManager, braintreeManager);
     return oneTimeDonationConfiguration.currencies()
         .entrySet().stream()
         .collect(Collectors.toMap(Entry::getKey, currencyAndConfig -> {
@@ -170,6 +169,7 @@ public class SubscriptionController {
                   levelIdAndConfig -> levelIdAndConfig.getValue().getPrices().get(currency).amount()));
 
           final List<String> supportedPaymentMethods = Arrays.stream(PaymentMethod.values())
+              .filter(paymentMethod -> !excludePaymentMethod(userAgent, paymentMethod))
               .filter(paymentMethod -> subscriptionProcessorManagers.stream()
                   .anyMatch(manager -> manager.supportsPaymentMethod(paymentMethod)
                       && manager.getSupportedCurrenciesForPaymentMethod(paymentMethod).contains(currency)))
@@ -185,9 +185,18 @@ public class SubscriptionController {
         }));
   }
 
-  @VisibleForTesting
-  GetSubscriptionConfigurationResponse buildGetSubscriptionConfigurationResponse(List<Locale> acceptableLanguages) {
+  // This logic to exclude some iOS client versions from receiving SEPA_DEBIT
+  // as a supported payment method can be removed after 01-23-24.
+  private boolean excludePaymentMethod(@Nullable final UserAgent userAgent, final PaymentMethod paymentMethod) {
+    return paymentMethod == PaymentMethod.SEPA_DEBIT
+        && userAgent != null
+        && userAgent.getPlatform() == ClientPlatform.IOS
+        && userAgent.getVersion().isLowerThanOrEqualTo(LAST_PROBLEMATIC_IOS_VERSION);
+  }
 
+  @VisibleForTesting
+  GetSubscriptionConfigurationResponse buildGetSubscriptionConfigurationResponse(final List<Locale> acceptableLanguages,
+      final UserAgent userAgent) {
     final Map<String, LevelConfiguration> levels = new HashMap<>();
 
     subscriptionConfiguration.getLevels().forEach((levelId, levelConfig) -> {
@@ -215,7 +224,7 @@ public class SubscriptionController {
                 giftBadge,
                 oneTimeDonationConfiguration.gift().expiration())));
 
-    return new GetSubscriptionConfigurationResponse(currencyConfiguration, levels);
+    return new GetSubscriptionConfigurationResponse(buildCurrencyConfiguration(userAgent), levels);
   }
 
   @DELETE
@@ -563,10 +572,18 @@ public class SubscriptionController {
   @GET
   @Path("/configuration")
   @Produces(MediaType.APPLICATION_JSON)
-  public CompletableFuture<Response> getConfiguration(@Context ContainerRequestContext containerRequestContext) {
+  public CompletableFuture<Response> getConfiguration(@Context ContainerRequestContext containerRequestContext,
+      @HeaderParam(HttpHeaders.USER_AGENT) final String userAgentString) {
     return CompletableFuture.supplyAsync(() -> {
       List<Locale> acceptableLanguages = getAcceptableLanguagesForRequest(containerRequestContext);
-      return Response.ok(buildGetSubscriptionConfigurationResponse(acceptableLanguages)).build();
+
+      UserAgent userAgent;
+      try {
+        userAgent = UserAgentUtil.parseUserAgentString(userAgentString);
+      } catch (UnrecognizedUserAgentException e) {
+        userAgent = null;
+      }
+      return Response.ok(buildGetSubscriptionConfigurationResponse(acceptableLanguages, userAgent)).build();
     });
   }
 
diff --git a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java
index ada09604864fd2d6bd5519513081f002b6930cc8..1594de87e78b863657c7a98f6dba58be9ad3470c 100644
--- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java
+++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java
@@ -42,6 +42,7 @@ import java.util.function.Predicate;
 import java.util.stream.Stream;
 import javax.ws.rs.client.Entity;
 import javax.ws.rs.core.Response;
+import org.apache.http.HttpHeaders;
 import org.assertj.core.api.InstanceOfAssertFactories;
 import org.glassfish.jersey.server.ServerProperties;
 import org.glassfish.jersey.test.grizzly.GrizzlyWebTestContainerFactory;
@@ -92,22 +93,6 @@ class SubscriptionControllerTest {
   private static final StripeManager STRIPE_MANAGER = mock(StripeManager.class);
   private static final BraintreeManager BRAINTREE_MANAGER = mock(BraintreeManager.class);
   private static final PaymentIntent PAYMENT_INTENT = mock(PaymentIntent.class);
-
-  static {
-    // this behavior is required by the SubscriptionController constructor
-    List.of(STRIPE_MANAGER, BRAINTREE_MANAGER)
-        .forEach(manager -> {
-          when(manager.supportsPaymentMethod(any()))
-              .thenCallRealMethod();
-        });
-    when(STRIPE_MANAGER.getSupportedCurrenciesForPaymentMethod(PaymentMethod.CARD))
-        .thenReturn(Set.of("usd", "jpy", "bif", "eur"));
-    when(STRIPE_MANAGER.getSupportedCurrenciesForPaymentMethod(PaymentMethod.SEPA_DEBIT))
-        .thenReturn(Set.of("eur"));
-    when(BRAINTREE_MANAGER.getSupportedCurrenciesForPaymentMethod(PaymentMethod.PAYPAL))
-        .thenReturn(Set.of("usd", "jpy"));
-  }
-
   private static final ServerZkReceiptOperations ZK_OPS = mock(ServerZkReceiptOperations.class);
   private static final IssuedReceiptsManager ISSUED_RECEIPTS_MANAGER = mock(IssuedReceiptsManager.class);
   private static final BadgeTranslator BADGE_TRANSLATOR = mock(BadgeTranslator.class);
@@ -134,6 +119,18 @@ class SubscriptionControllerTest {
 
     when(STRIPE_MANAGER.getProcessor()).thenReturn(SubscriptionProcessor.STRIPE);
     when(BRAINTREE_MANAGER.getProcessor()).thenReturn(SubscriptionProcessor.BRAINTREE);
+
+    List.of(STRIPE_MANAGER, BRAINTREE_MANAGER)
+        .forEach(manager -> {
+          when(manager.supportsPaymentMethod(any()))
+              .thenCallRealMethod();
+        });
+    when(STRIPE_MANAGER.getSupportedCurrenciesForPaymentMethod(PaymentMethod.CARD))
+        .thenReturn(Set.of("usd", "jpy", "bif", "eur"));
+    when(STRIPE_MANAGER.getSupportedCurrenciesForPaymentMethod(PaymentMethod.SEPA_DEBIT))
+        .thenReturn(Set.of("eur"));
+    when(BRAINTREE_MANAGER.getSupportedCurrenciesForPaymentMethod(PaymentMethod.PAYPAL))
+        .thenReturn(Set.of("usd", "jpy"));
   }
 
   @Test
@@ -713,8 +710,9 @@ class SubscriptionControllerTest {
     );
   }
 
-  @Test
-  void getSubscriptionConfiguration() {
+  @ParameterizedTest
+  @MethodSource
+  void getSubscriptionConfiguration(final String userAgent, final boolean expectSepa) {
     when(BADGE_TRANSLATOR.translate(any(), eq("B1"))).thenReturn(new Badge("B1", "cat1", "name1", "desc1",
         List.of("l", "m", "h", "x", "xx", "xxx"), "SVG",
         List.of(new BadgeSvg("sl", "sd"), new BadgeSvg("ml", "md"), new BadgeSvg("ll", "ld"))));
@@ -736,6 +734,7 @@ class SubscriptionControllerTest {
 
     GetSubscriptionConfigurationResponse response = RESOURCE_EXTENSION.target("/v1/subscription/configuration")
         .request()
+        .header(HttpHeaders.USER_AGENT, userAgent)
         .get(GetSubscriptionConfigurationResponse.class);
 
     assertThat(response.currencies()).containsKeys("usd", "jpy", "bif", "eur").satisfies(currencyMap -> {
@@ -791,7 +790,8 @@ class SubscriptionControllerTest {
                 List.of(BigDecimal.valueOf(5))));
         assertThat(currency.subscription()).isEqualTo(
             Map.of("5", BigDecimal.valueOf(5), "15", BigDecimal.valueOf(15),"35", BigDecimal.valueOf(35)));
-        assertThat(currency.supportedPaymentMethods()).isEqualTo(List.of("CARD", "SEPA_DEBIT"));
+        final List<String> expectedPaymentMethods = expectSepa ? List.of("CARD", "SEPA_DEBIT") : List.of("CARD");
+        assertThat(currency.supportedPaymentMethods()).isEqualTo(expectedPaymentMethods);
       });
     });
 
@@ -841,6 +841,7 @@ class SubscriptionControllerTest {
     // subscription levels are Badge, while one-time levels are PurchasableBadge, which adds `duration`
     Map<String, Object> genericResponse = RESOURCE_EXTENSION.target("/v1/subscription/configuration")
         .request()
+        .header(HttpHeaders.USER_AGENT, userAgent)
         .get(Map.class);
 
     assertThat(genericResponse.get("levels")).satisfies(levels -> {
@@ -863,6 +864,19 @@ class SubscriptionControllerTest {
     });
   }
 
+  private static Stream<Arguments> getSubscriptionConfiguration() {
+    return Stream.of(
+        Arguments.of("Signal-iOS/6.44.0.8", false),
+        Arguments.of("Signal-iOS/6.45.0.0", true),
+        Arguments.of("Signal-iOS/6.45.0.2", true),
+        Arguments.of("Signal-iOS/6.46.0.0", true),
+        Arguments.of("Signal-Android/1.2.3", true),
+        Arguments.of(null, true),
+        Arguments.of("", true),
+        Arguments.of("definitely not a parseable user agent", true)
+    );
+  }
+
   /**
    * Encapsulates {@code static} configuration, to keep the class header simpler and avoid illegal forward references
    */