diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
index 1f9388fe9d7165fd05f62b3e768e44f973b914c3..50a701e76629744dd1fcb50690cc6354a39ef7f2 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/WhisperServerService.java
@@ -199,6 +199,7 @@ import org.whispersystems.textsecuregcm.storage.ReportMessageManager;
 import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
 import org.whispersystems.textsecuregcm.storage.VerificationSessionManager;
 import org.whispersystems.textsecuregcm.storage.VerificationSessions;
+import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator;
 import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
 import org.whispersystems.textsecuregcm.subscriptions.StripeManager;
 import org.whispersystems.textsecuregcm.util.DynamoDbFromConfig;
@@ -294,6 +295,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
         clock, config.getBadges(), headerControlledResourceBundleLookup);
     ResourceBundleLevelTranslator resourceBundleLevelTranslator = new ResourceBundleLevelTranslator(
         headerControlledResourceBundleLookup);
+    BankMandateTranslator bankMandateTranslator = new BankMandateTranslator(headerControlledResourceBundleLookup);
 
     DynamoDbAsyncClient dynamoDbAsyncClient = DynamoDbFromConfig.asyncClient(config.getDynamoDbClientConfiguration(),
         AWSSDK_CREDENTIALS_PROVIDER);
@@ -804,7 +806,7 @@ public class WhisperServerService extends Application<WhisperServerConfiguration
     if (config.getSubscription() != null && config.getOneTimeDonations() != null) {
       commonControllers.add(new SubscriptionController(clock, config.getSubscription(), config.getOneTimeDonations(),
           subscriptionManager, stripeManager, braintreeManager, zkReceiptOperations, issuedReceiptsManager, profileBadgeConverter,
-          resourceBundleLevelTranslator));
+          resourceBundleLevelTranslator, bankMandateTranslator));
     }
 
     for (Object controller : commonControllers) {
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 5a15c0fa6d6f3f68245958d4907d47ac82848b51..45983b8758a958d2469f7c540963444c1caac650 100644
--- a/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/controllers/SubscriptionController.java
@@ -87,6 +87,8 @@ import org.whispersystems.textsecuregcm.metrics.UserAgentTagUtil;
 import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
 import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
 import org.whispersystems.textsecuregcm.storage.SubscriptionManager.GetResult;
+import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator;
+import org.whispersystems.textsecuregcm.subscriptions.BankTransferType;
 import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
 import org.whispersystems.textsecuregcm.subscriptions.ChargeFailure;
 import org.whispersystems.textsecuregcm.subscriptions.PaymentMethod;
@@ -117,6 +119,7 @@ public class SubscriptionController {
   private final IssuedReceiptsManager issuedReceiptsManager;
   private final BadgeTranslator badgeTranslator;
   private final LevelTranslator levelTranslator;
+  private final BankMandateTranslator bankMandateTranslator;
   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");
@@ -135,7 +138,8 @@ public class SubscriptionController {
       @Nonnull ServerZkReceiptOperations zkReceiptOperations,
       @Nonnull IssuedReceiptsManager issuedReceiptsManager,
       @Nonnull BadgeTranslator badgeTranslator,
-      @Nonnull LevelTranslator levelTranslator) {
+      @Nonnull LevelTranslator levelTranslator,
+      @Nonnull BankMandateTranslator bankMandateTranslator) {
     this.clock = Objects.requireNonNull(clock);
     this.subscriptionConfiguration = Objects.requireNonNull(subscriptionConfiguration);
     this.oneTimeDonationConfiguration = Objects.requireNonNull(oneTimeDonationConfiguration);
@@ -146,6 +150,7 @@ public class SubscriptionController {
     this.issuedReceiptsManager = Objects.requireNonNull(issuedReceiptsManager);
     this.badgeTranslator = Objects.requireNonNull(badgeTranslator);
     this.levelTranslator = Objects.requireNonNull(levelTranslator);
+    this.bankMandateTranslator = Objects.requireNonNull(bankMandateTranslator);
   }
 
   private Map<String, CurrencyConfiguration> buildCurrencyConfiguration(@Nullable final UserAgent userAgent) {
@@ -587,6 +592,20 @@ public class SubscriptionController {
     });
   }
 
+  @GET
+  @Path("/bank_mandate/{bankTransferType}")
+  @Produces(MediaType.APPLICATION_JSON)
+  public CompletableFuture<Response> getBankMandate(final @Context ContainerRequestContext containerRequestContext,
+      final @PathParam("bankTransferType") BankTransferType bankTransferType) {
+    return CompletableFuture.supplyAsync(() -> {
+      List<Locale> acceptableLanguages = getAcceptableLanguagesForRequest(containerRequestContext);
+      return Response.ok(new GetBankMandateResponse(
+          bankMandateTranslator.translate(acceptableLanguages, bankTransferType))).build();
+    });
+  }
+
+  public record GetBankMandateResponse(String mandate) {}
+
   public record GetBoostBadgesResponse(Map<Long, Level> levels) {
       public record Level(PurchasableBadge badge) {
       }
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BankMandateTranslator.java b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BankMandateTranslator.java
new file mode 100644
index 0000000000000000000000000000000000000000..438617b1a351075a5c6d5702251407f20e7caa2c
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BankMandateTranslator.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.subscriptions;
+
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+import java.util.ResourceBundle;
+import javax.annotation.Nonnull;
+import org.signal.i18n.HeaderControlledResourceBundleLookup;
+
+public class BankMandateTranslator {
+  private static final String BASE_NAME = "org.signal.bankmandate.BankMandate";
+  private final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup;
+
+  public BankMandateTranslator(
+      @Nonnull final HeaderControlledResourceBundleLookup headerControlledResourceBundleLookup) {
+    this.headerControlledResourceBundleLookup = Objects.requireNonNull(headerControlledResourceBundleLookup);
+  }
+
+  public String translate(final List<Locale> acceptableLanguages, final BankTransferType bankTransferType) {
+    final ResourceBundle resourceBundle = headerControlledResourceBundleLookup.getResourceBundle(BASE_NAME,
+        acceptableLanguages);
+    return resourceBundle.getString(getKey(bankTransferType));
+  }
+
+  private static String getKey(final BankTransferType bankTransferType) {
+    return switch (bankTransferType) {
+      case SEPA_DEBIT -> "SEPA_MANDATE";
+    };
+  }
+}
diff --git a/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BankTransferType.java b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BankTransferType.java
new file mode 100644
index 0000000000000000000000000000000000000000..58920269200cf8f1ba45b557bafb01dee482e83d
--- /dev/null
+++ b/service/src/main/java/org/whispersystems/textsecuregcm/subscriptions/BankTransferType.java
@@ -0,0 +1,10 @@
+/*
+ * Copyright 2023 Signal Messenger, LLC
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+package org.whispersystems.textsecuregcm.subscriptions;
+
+public enum BankTransferType {
+  SEPA_DEBIT
+}
diff --git a/service/src/main/resources/org/signal/bankmandate/BankMandate.properties b/service/src/main/resources/org/signal/bankmandate/BankMandate.properties
new file mode 100644
index 0000000000000000000000000000000000000000..e9492beda4b764feba8369f2df092172dec2adad
--- /dev/null
+++ b/service/src/main/resources/org/signal/bankmandate/BankMandate.properties
@@ -0,0 +1,7 @@
+#
+# Copyright 2023 Signal Messenger, LLC
+# SPDX-License-Identifier: AGPL-3.0-only
+#
+
+SEPA_MANDATE = By providing your payment information and confirming this payment, you authorise (A) Signal Technology Foundation and Stripe, our payment service provider, to send instructions to your bank to debit your account and (B) your bank to debit your account in accordance with those instructions. As part of your rights, you are entitled to a refund from your bank under the terms and conditions of your agreement with your bank. A refund must be claimed within 8 weeks starting from the date on which your account was debited. Your rights are explained in a statement that you can obtain from your bank. You agree to receive notifications for future debits up to 2 days before they occur.
+
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 1594de87e78b863657c7a98f6dba58be9ad3470c..98f61156b01f493d4f14a85b9bcd7b916541b878 100644
--- a/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java
+++ b/service/src/test/java/org/whispersystems/textsecuregcm/controllers/SubscriptionControllerTest.java
@@ -60,6 +60,7 @@ import org.whispersystems.textsecuregcm.badges.BadgeTranslator;
 import org.whispersystems.textsecuregcm.badges.LevelTranslator;
 import org.whispersystems.textsecuregcm.configuration.OneTimeDonationConfiguration;
 import org.whispersystems.textsecuregcm.configuration.SubscriptionConfiguration;
+import org.whispersystems.textsecuregcm.controllers.SubscriptionController.GetBankMandateResponse;
 import org.whispersystems.textsecuregcm.controllers.SubscriptionController.GetSubscriptionConfigurationResponse;
 import org.whispersystems.textsecuregcm.entities.Badge;
 import org.whispersystems.textsecuregcm.entities.BadgeSvg;
@@ -67,6 +68,7 @@ import org.whispersystems.textsecuregcm.mappers.CompletionExceptionMapper;
 import org.whispersystems.textsecuregcm.mappers.SubscriptionProcessorExceptionMapper;
 import org.whispersystems.textsecuregcm.storage.IssuedReceiptsManager;
 import org.whispersystems.textsecuregcm.storage.SubscriptionManager;
+import org.whispersystems.textsecuregcm.subscriptions.BankMandateTranslator;
 import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager;
 import org.whispersystems.textsecuregcm.subscriptions.BraintreeManager.PayPalOneTimePaymentApprovalDetails;
 import org.whispersystems.textsecuregcm.subscriptions.ChargeFailure;
@@ -97,9 +99,10 @@ class SubscriptionControllerTest {
   private static final IssuedReceiptsManager ISSUED_RECEIPTS_MANAGER = mock(IssuedReceiptsManager.class);
   private static final BadgeTranslator BADGE_TRANSLATOR = mock(BadgeTranslator.class);
   private static final LevelTranslator LEVEL_TRANSLATOR = mock(LevelTranslator.class);
+  private static final BankMandateTranslator BANK_MANDATE_TRANSLATOR = mock(BankMandateTranslator.class);
   private static final SubscriptionController SUBSCRIPTION_CONTROLLER = new SubscriptionController(
       CLOCK, SUBSCRIPTION_CONFIG, ONETIME_CONFIG, SUBSCRIPTION_MANAGER, STRIPE_MANAGER, BRAINTREE_MANAGER, ZK_OPS,
-      ISSUED_RECEIPTS_MANAGER, BADGE_TRANSLATOR, LEVEL_TRANSLATOR);
+      ISSUED_RECEIPTS_MANAGER, BADGE_TRANSLATOR, LEVEL_TRANSLATOR, BANK_MANDATE_TRANSLATOR);
   private static final ResourceExtension RESOURCE_EXTENSION = ResourceExtension.builder()
       .addProperty(ServerProperties.UNWRAP_COMPLETION_STAGE_IN_WRITER_ENABLE, Boolean.TRUE)
       .addProvider(AuthHelper.getAuthFilter())
@@ -710,6 +713,24 @@ class SubscriptionControllerTest {
     );
   }
 
+  @Test
+  void testGetBankMandate() {
+    when(BANK_MANDATE_TRANSLATOR.translate(any(), any())).thenReturn("bankMandate");
+    final Response response = RESOURCE_EXTENSION.target("/v1/subscription/bank_mandate/sepa_debit")
+        .request()
+        .get();
+    assertThat(response.getStatus()).isEqualTo(200);
+    assertThat(response.readEntity(GetBankMandateResponse.class).mandate()).isEqualTo("bankMandate");
+  }
+
+  @Test
+  void testGetBankMandateInvalidBankTransferType() {
+    final Response response = RESOURCE_EXTENSION.target("/v1/subscription/ach")
+        .request()
+        .get();
+    assertThat(response.getStatus()).isEqualTo(404);
+  }
+
   @ParameterizedTest
   @MethodSource
   void getSubscriptionConfiguration(final String userAgent, final boolean expectSepa) {