Christopher Orr

What you need to know about mandates on Stripe

If you’re using certain types of payment method for your business on Stripe, you’ll need to deal with mandates: the authorisation you get from your customer, that enables you to collect payments, whether now or in the future.

Mandates are not extensively documented, and near-invisible on the Stripe Dashboard. Thankfully, all you really need to know is how to deal with a mandate being cancelled.

🔥 Doing so will save you customer support effort, and perhaps help avoid some fees!

🔍  TL;DR — Make sure you're handling the mandate.updated event. See below.

This article is based on my experience, primarily with recurring payment method types that are available in Europe. As we’ll see, experiences may vary. Feedback is welcome!

What are Stripe mandates?

On Stripe, the Mandate object is a record of when and where a customer accepted a particular set of terms — whether by giving you physical written permission, or by agreeing to some terms online. For example, Stripe Checkout may show a notice like this when choosing a direct debit payment method:

By clicking Subscribe, you authorise (A) Acme Corporation 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.

Typically any bank direct debit (e.g. ACH, Bacs, BECS, SEPA) will require a mandate, but other recurring payment methods also use them, including PayPal. For the majority of payment methods, mandates are not something you need to think about.

In addition to these typical “multi-use” mandates which allow taking multiple payments over time, Stripe also supports single-use mandates for when you only need to take one-off payment from certain payment method types.

Each Mandate object has its own lifecycle, and is associated only with a single Payment Method object — they’re not directly tied to a Customer, Subscription, or Invoice etc.

Expand to see an example of a Stripe Mandate object.

Note that its only connection to any other Stripe object is the payment_method ID:

{
  "id": "mandate_1QTvnvCxlkloln0peLvVkh3a",
  "object": "mandate",
  "customer_acceptance": {
    "accepted_at": 1732883696,
    "online": {
      "ip_address": "10.11.12.13",
      "user_agent": "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0"
    },
    "type": "online"
  },
  "payment_method": "pm_1QTvnsCxloln0peAH63WhpO",
  "payment_method_details": {
    "paypal": {
      "billing_agreement_id": "B-3FB84926DA190104",
      "fingerprint": "c6d0aef08633509",
      "payer_id": "5JXY69017MKZ",
      "verified_email": "my-paypal-account@example.com"
    },
    "type": "paypal"
  },
  "status": "active",
  "type": "multi_use"
}

Why do I need to care about Stripe mandates?

If a customer sets up a payment method with a mandate on Stripe, but later revokes that mandate, this can be a good indication that they want to cancel their subscription.

Stripe will notify you when a mandate is updated, but will not automatically cancel any subscriptions the customer has. This makes sense, but there can be some negative consequences to ignoring mandate updates, perhaps even costing you money.

1️⃣ You might annoy your customers

For example: a subscription customer who signed up using PayPal can later revoke their recurring payment mandate by logging into their PayPal account. Some people just find it convenient to cancel or block payments via their issuer — especially if your product doesn’t provide an easy self-service cancellation flow.

However, when the next subscription billing cycle starts for this example customer, Stripe will still attempt to charge the associated PayPal account, despite the lack of active mandate. As you might expect, this will be blocked by PayPal, and will trigger an invoice.payment_failed event from Stripe.

The customer — who believes they have ended their subscription — may receive an automated “payment failed” notification from you, and perhaps even a push notification from PayPal. This may lead to an annoyed customer support request, asking why you’re trying to take money from them despite having cancelled their subscription. Before I figured out how mandates work, I saw this happen many times… 😬

2️⃣ You might be charged payment failure fees

In the worst case, ignoring mandate revocation might cost you money. While PayPal doesn’t charge you for attempting to take payment from an account without an active mandate, banks tend to be less forgiving.

In the Stripe documentation, there’s little mention of mandate revocation, and seemingly no documentation on which payment types will actively send you a notification in case a customer cancels their mandate.

So it’s feasible that a customer could cancel their mandate with their bank, which may notify Stripe, triggering a mandate.updated event. If you ignore this event, Stripe might attempt to charge the bank account in future, which will fail — causing the bank to charge Stripe, which gets passed on to you as a failure fee.

I can say that, so far, I have not observed any advance notification of SEPA direct debit mandates being revoked. It appears to happen after a failed attempt to take payment. That is, the banks do not seem to notify Stripe when their customer revokes a mandate, which perhaps is to be expected In such cases, you just have to accept that you’ll be charged a payment failure fee.

Hopefully there are not any payment methods types where Stripe ignores a known mandate cancellation (like it does for PayPal), that also have failure fees, but it’s hard to know for sure without it being documented.

So what should I do when a mandate is cancelled on Stripe?

What you’ll want to do in such cases will depend on your business model — but if a customer has cancelled their payments, and hasn’t provided a new payment method (e.g. via the Stripe customer portal), then you should probably accept their intent and cancel the associated subscription at the end of the current billing cycle. Or at least double-check with the customer, and perhaps ask them to add a new payment method.

This is up to you to implement, by handling the mandate.updated event appropriately.

You can skip to the implementation guide below, or read on for more background.

How are mandates created on Stripe?

There is no API to create a Mandate object directly. Stripe Checkout can do this on your behalf when necessary, which is the simplest way to do it. Underlying Checkout are the Payment Intent & Setup Intent APIs, which are what actually create a Mandate object.

You can also use those APIs directly, and pass in what information you have about the customer’s acceptance of your mandate terms when confirming the intent.

The documentation varies quite a bit between payment methods when it comes to explaining mandates. For most cases when using these APIs yourself, you probably only need to provide the customer acceptance information you’ve collected.

As is often the case with the Stripe API — especially when working with non-card payment methods — it’s worthwhile to experiment with the various API fields to understand what’s actually necessary.

You may see error messages like this, if Stripe requires you to share evidence of your customer accepting your mandate terms:

This PaymentIntent requires a mandate, but no existing mandate was found. Collect mandate acceptance from the customer and try again, providing acceptance data in the mandate_data parameter.

When confirming a PaymentIntent with a sepa_debit PaymentMethod and setup_future_usage, mandate_data is required.

The lifecycle of a Stripe mandate

Mandates come into existence without much fanfare. When confirming a Setup Intent for a payment method that requires a mandate, you’ll see the mandate ID field in the API response. Otherwise, there’s no Stripe event to indicate that a mandate was created, and there don’t appear to be any other API response fields that include the mandate ID.

Flowchart showing Stripe mandates being created, and eventually transitioning to inactive

After creation, mandates can transition through three states

Upon creation, the status of a Mandate object will be either pending or active.

An active mandate is what you want: customer consent has been given, and it should be possible to charge the associated payment method — as far as Stripe is aware.

The pending status is typically for online payment method types that need additional authentication, e.g. logging in to a PayPal account and agreeing to future automated payments. Once this is done, Stripe will update the mandate’s status to active.

Finally, a mandate can enter the inactive state, which may happen:

  1. When a pending mandate expires after a period of time;
    • For example, if the customer fails to complete additional authentication, and their checkout session expires.
  2. When Stripe receives explicit notification that a mandate has been cancelled;
    • Some payment method types notify Stripe when a customer revokes permission, e.g. cancelling an automatic payment via their PayPal account.
  3. When Stripe tries to charge a payment method, but the other party indicates that the mandate is now invalid, e.g. the customer revoked permission via their bank;
    • For example, with SEPA direct debit: Stripe may receive no notification of a mandate being cancelled or blocked. Stripe then attempts to take payment from the bank account, which fails due to lack of a valid mandate. Stripe subsequently charges you a failure fee, and marks the mandate as inactive.
  4. When the payment method tied to a mandate is detached from a customer;
  5. When a customer is deleted, and becomes detached from their payment methods.

(If there are other scenarios where this can happen, I’d be interested to know!)

As we’ll discuss later, whenever the status value of a mandate changes, you can be notified via the mandate.updated event from Stripe.

Where can I see mandates and their status on Stripe?

It is not straightforward to find out whether a Stripe customer has a mandate, or what its current state is. Mostly this isn’t too problematic, but it would be nice to have the full picture about each customer and their payments. I’ve certainly dealt with customer support cases where someone claims they’ve cancelled their subscription via PayPal, but actually haven’t. The Stripe Dashboard doesn’t make it easy to dig into the details.

Stripe Dashboard

The Stripe Dashboard doesn’t provide a dedicated list of mandates, like it does for transactions, customers, etc.

The Customer page doesn’t help much either. I would expect to find information and updates about a customer’s mandates under the “Payment methods” or “Events” sections. In my experience, that’s not the case.

Payment methods on the Customer page

Each payment method type seems to be implemented differently, so perhaps some will actually show mandate information directly in this section. However, the best starting point in many cases, while not hugely obvious, will be the “Set up for future use” intent:

Payment Method information for a SEPA direct debit payment, showing a 'Set up for future use' link

Payment Intent detail page

Indeed, the most likely place to find a mandate ID mentioned is where the payment method was first set up for later off-session usage.

This will include mandates created via a checkout session, but also payment intents created via the API. Similar to the payment method above, the payment intent page will indicate in its timeline that a particular payment method was set up for future use:

Payment Intent timeline for a PayPal payment, where it says it was 'set up for future off-session payments'

A little further down the page, you should see the payment method associated with this payment. But again this depends on the payment method type — for example, this section is currently not shown when PayPal is used.

But when this section is present, you may find an elusive mandate ID:

Payment Method section on a Payment Intent page, showing SEPA details and a clickable mandate ID

To access this information programmatically, it can be found within the associated Charge object (latest_charge), rather than as part of the payment intent itself…

Again, this can differ between payment methods. While most types have some sort of mandate information within the payment_method_details of a Charge (e.g. iDEAL, SEPA), others like Link or PayPal don’t provide any mandate information.

Similarly, if a mandate ID is shown, what it links to depends on the payment method type. In screenshot above, showing a SEPA direct debit payment, the mandate link points to a publicly accessible page, which does not show the mandate’s current state. It’s mostly a static record of when the mandate terms were accepted, and to which person and bank account they apply.

Setup Intent detail page

If a Setup Intent was used to add the payment method to a customer — whether via a checkout session, or via the API — then the intent page should show the mandate ID:

Setup Intent details for a Link payment method, showing a non-clickable mandate ID

The same as with Payment Intents, only certain payment method types will show a link to further mandate details.

To access this information programmatically, as mentioned above, you can check the payment_method and mandate ID fields of the Setup Intent.

An exception is payment method types like Bancontact or iDEAL. These use a bank redirect, ask your customer to confirm recurring payments, but then actually attach a SEPA direct debit payment method to the Customer object for future payments. In this case, there is no mandate ID attached to the Setup Intent — instead you have to look up the lesser-documented payment_method_details field of the associated Setup Attempt…

Stripe API

There is a Stripe API endpoint to retrieve a Mandate object but, as you might expect, this requires that you already know the mandate ID.

As discussed in the previous sections, if you have a Payment Intent or Setup Intent ID, you can then follow the various paths (where available) to fetch the mandate ID.

Otherwise, the relationship from Mandate to Payment Method is unidirectional: there is no way to locate mandates given a Customer object or Payment Method object. Similarly, there’s no “mandate created” event that you can listen for.

How to handle Stripe mandate updates

To deal with cancelled mandates, you need to:

  1. Ask Stripe to send the mandate.updated event to your backend, e.g. via webhook;
  2. Include only events where a mandate transitioned to inactive;
  3. Filter out irrelevant cases, e.g. customer deleted, or old payment method detached;
  4. Take action, e.g. inform the customer, pause their subscription.

1️⃣ Receive “mandate.updated” events from Stripe

Configure a new or existing Stripe webhook to send the mandate.updated event type. Stripe will now inform your webhook endpoint whenever a mandate is updated.

Expand to see an example of a Stripe mandate.updated event:

This event represents a PayPal mandate that moved from activeinactive.

{
  "id": "evt_1QgRGBCxlkTaLKpvZqsw0F95",
  "object": "event",
  "api_version": "2022-11-15",
  "created": 1732883696,
  "data": {
    "object": {
      "id": "mandate_1QTvnvCxlkloln0peLvVkh3a",
      "object": "mandate",
      "customer_acceptance": {
        "accepted_at": 1732883696,
        "online": {
          "ip_address": "10.11.12.13",
          "user_agent": "Mozilla/5.0 (Linux; Android 14) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0"
        },
        "type": "online"
      },
      "livemode": true,
      "payment_method": "pm_1QTvnsCxloln0peAH63WhpO",
      "payment_method_details": {
        "paypal": {
          "billing_agreement_id": "B-3FB84926DA190104",
          "fingerprint": "c6d0aef08633509",
          "payer_id": "5JXY69017MKZ",
          "verified_email": "my-paypal-account@example.com"
        },
        "type": "paypal"
      },
      "status": "inactive",
      "type": "multi_use"
    },
    "previous_attributes": {
      "status": "active"
    }
  },
  "livemode": true,
  "pending_webhooks": 1,
  "request": {
    "id": "req_fK87GBpb0J2Ajj",
    "idempotency_key": null
  },
  "type": "mandate.updated"
}

2️⃣ Include only events where a mandate was cancelled

You will now receive all mandate updates from Stripe, but only those which change from the status activeinactive are relevant here.

The event’s previous_attributes can be used to figure out which transition took place:

// Pseudocode for a Stripe webhook handler, checking for cancellation
if (event.type == "mandate.updated") {
  val mandate = event.object
  if (event.data.previousAttributes["status"] == "active"
      && mandate.status == "inactive") {
    handleMandateCancelled(mandate)
  }
}

3️⃣ Filter out irrelevant cases

As mentioned earlier, mandates can enter the inactive state for various reasons. Perhaps the customer switched to a new payment method and is removing the old one via the customer portal. Or maybe the customer was deleted via the Dashboard.

You only need to process the event if you’ll be unable to charge the customer again, i.e. the mandate is tied to the default payment method of the Customer or Subscription:

// Pseudocode to determine whether the mandate cancellation is relevant
fun handleMandateCancelled(mandate: Mandate) {
  // Grab the payment method associated with the mandate
  val paymentMethod = PaymentMethod.retrieve(mandate.paymentMethod)

  // If the payment method is not attached to a customer, do nothing:
  // - You deleted the payment method via the Dashboard; or
  // - The customer removed the payment method from the portal
  val customerId = paymentMethod.customer ?: return

  // If the customer somehow doesn't exist, or was deleted, do nothing
  val customer = Customer.retrieve(customerId)
  if (customer == null || customer.deleted) return

  // If the mandate is not for a default payment method, do nothing:
  // the Customer/Subscription still has a method that can be charged
  val subscription = getActiveSubscription(customer)
  if (customer.defaultPaymentMethod != paymentMethod
      && subscription.defaultPaymentMethod != paymentMethod) return

  // If we make it to this point, we should take action
  actOnMandateCancellation(customer, subscription)
}

4️⃣ Take action

You now know that the mandate.updated event was triggered because the default payment method of the customer had its mandate revoked – whether directly or indirectly. Either way, this payment method can’t be charged again.

Again, how to act will depend on your business model, but this scenario can be a good indicator that your customer wants to cancel their subscription. Or at least pause it.

If you do nothing at this point, Stripe will keep trying in future to charge the now mandate-less payment method.

fun actOnMandateCancellation(customer: Customer, subscription: Subscription) {
  // Set the subscription to cancel at the end of the current billing cycle
  val builder = SubscriptionUpdateParams.builder().setCancelAtPeriodEnd(true)
  subscription.update(builder.build())

  // Inform the customer, and perhaps ask them to add a new payment method,
  // or prompt them to downgrade their subscription plan rather than leaving
  sendPaymentMethodMissingEmail(customer.email)
}

How do I test Stripe mandate update behaviour?

The short answer is: in production.

While Stripe provides some great tools such as its time-travelling test clocks, most of the utilities available relate to one-time payments, payment failures when setting up a subscription, or to various card-related failure modes.

Stripe doesn’t provide a way to induce subsequent subscription payment failures in test mode for non-card payment methods. It’s also not possible to send a mandate.updated event using the stripe trigger CLI command.

You can test your backend business logic by generating various event JSON payloads, based on real mandate.updated events, and potentially mocking some customer and payment method state.

But it’s tough to know all the ways in which payments can fail in practice. So it’s always worth monitoring your Stripe payment failures in production for any unexpected cases.

Final thoughts

If you’re a Stripe user working with these sorts of payment methods types, I hope that this (somewhat lengthy) attempt at a missing manual for mandates is useful for you!

If I had a wishlist on this topic for the Stripe platform, it would include:

  • Show mandates, their status, and any updates on the associated Customer page;
    • Even if you’re aware that mandates exist(!), it’s currently very difficult to locate any related details for a given Customer.
    • Perhaps show a more useful event summary than “The multi_use mandate with ID mandate_1QTvnvCxlkloln0peLvVkh3a was updated” for every event.
  • Provide a more consistent experience on the Dashboard across payment methods;
    • e.g. the case where PayPal details are absent from the Payment Intent page.
    • I can imagine that payment methods were added at different times by different teams, but as a Dashboard user a more seamless experience would be nice.
  • Add documentation on mandates, their lifecycle, when updates can specifically occur, and the consequences;
    • e.g. perhaps an overarching page on mandates, plus concrete information on when revocations happen (plus any potential fees) for each payment method.
  • Potentially a specific event type for mandate revocation could be useful;
    • It’s also unclear whether thin events will (eventually) remove our ability to detect status changes by inspecting the event’s previous_attributes.
  • Mandates could potentially be more visible in the API;
    • Could we perhaps we see mandate info in an intent object directly, rather than having to grab the latest_attempt or latest_charge (why Charges? 👴🏻)?
  • Make it possible to trigger payment failure for a recurring payment in test mode, particularly with non-card payment methods.

But overall I’m grateful that Stripe makes everything work as smoothly as it does — across a multitude of payment types, and with so much data available to us (in real time!), whether via the shiny Stripe Dashboard, API, or CLI. I was also glad to finally discover the Stripe Developers Discord, populated with some very knowledgeable Stripe folk.

Finally, as briefly mentioned, this article is based on my experience over several years of building and maintaining mostly subscription-based Stripe integrations. There are many gaps in my knowledge — including pretty much any non-European payment methods — so I would be grateful for any hints or feedback!