# How to enable SEPA Direct Debit in Stripe

- Tool: Stripe
- Difficulty: Intermediate
- Time required: 30 minutes
- Compatibility: Stripe API 2024-12+, Node.js 18+, SEPA zone (EUR currency)
- Last updated: March 2026

## TL;DR

Enable SEPA Direct Debit in Stripe by activating it in the Dashboard, collecting an IBAN and mandate acceptance from your customer, and confirming a PaymentIntent with the sepa_debit payment method type. SEPA payments take 5-14 business days to settle and require mandate management for compliance.

## Accepting SEPA Direct Debit Payments with Stripe

SEPA Direct Debit allows you to collect euro payments from bank accounts across the 36 SEPA countries. Unlike card payments, SEPA debits are not confirmed instantly — they take several business days to settle and can be disputed after the fact. This tutorial covers activating SEPA in Stripe, collecting the customer's IBAN, displaying the mandate, and handling the asynchronous payment lifecycle.

## Before you start

- A Stripe account based in a SEPA-supported country or with EUR payouts enabled
- Your Stripe secret key (sk_test_...) and publishable key (pk_test_...)
- A Node.js backend with Express
- Stripe.js loaded on your frontend

## Step-by-step guide

### 1. Enable SEPA Direct Debit in the Stripe Dashboard

Go to Stripe Dashboard → Settings → Payment methods. Find 'SEPA Direct Debit' and enable it. You may need to accept additional terms related to SEPA mandates. SEPA is only available for EUR currency.

**Expected result:** SEPA Direct Debit shows as 'Active' in your Dashboard payment methods.

### 2. Create a PaymentIntent for SEPA on the server

Create a PaymentIntent specifying sepa_debit as the payment method type and EUR as the currency. Amounts are in cents — 1000 means 10.00 EUR.

```
// server.js
const express = require('express');
const Stripe = require('stripe');
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
const app = express();
app.use(express.json());

app.post('/api/create-sepa-intent', async (req, res) => {
  try {
    const intent = await stripe.paymentIntents.create({
      amount: req.body.amount,  // in cents
      currency: 'eur',
      payment_method_types: ['sepa_debit'],
      mandate_data: {
        customer_acceptance: {
          type: 'online',
          online: {
            ip_address: req.ip,
            user_agent: req.headers['user-agent']
          }
        }
      }
    });
    res.json({ client_secret: intent.client_secret });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

app.listen(3001);
```

**Expected result:** The endpoint returns a client_secret for a SEPA PaymentIntent.

### 3. Collect the IBAN on the frontend

Use the Stripe IBAN Element to collect the customer's bank account number. The IBAN Element validates the format automatically and shows the bank name.

```
const stripe = Stripe('pk_test_your_publishable_key');
const elements = stripe.elements();
const ibanElement = elements.create('iban', {
  supportedCountries: ['SEPA'],
  placeholderCountry: 'DE'
});
ibanElement.mount('#iban-element');

// Display mandate text to the customer
const mandateText = document.getElementById('mandate-text');
mandateText.textContent = 'By providing your IBAN and confirming this payment, you authorize (your company) and Stripe, our payment service provider, to send instructions to your bank to debit your account in accordance with those instructions.';
```

**Expected result:** An IBAN input field renders with validation. The mandate text is displayed below the field.

### 4. Confirm the SEPA payment

When the customer submits the form, confirm the PaymentIntent with the IBAN element and customer details. SEPA payments enter 'processing' status immediately — they do not succeed instantly.

```
const form = document.getElementById('sepa-form');
form.addEventListener('submit', async (e) => {
  e.preventDefault();

  const res = await fetch('/api/create-sepa-intent', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ amount: 1000 })
  });
  const { client_secret } = await res.json();

  const { error, paymentIntent } = await stripe.confirmSepaDebitPayment(
    client_secret,
    {
      payment_method: {
        sepa_debit: { iban: ibanElement },
        billing_details: {
          name: document.getElementById('name').value,
          email: document.getElementById('email').value
        }
      }
    }
  );

  if (error) {
    console.error(error.message);
  } else {
    // paymentIntent.status will be 'processing'
    console.log('Payment submitted:', paymentIntent.id);
  }
});
```

**Expected result:** The PaymentIntent status changes to 'processing'. The payment will settle in 5-14 business days.

### 5. Handle SEPA webhook events

SEPA payments are asynchronous. Listen for payment_intent.succeeded (funds received) and payment_intent.payment_failed (debit failed or disputed) to update your order status.

```
// In your webhook handler:
switch (event.type) {
  case 'payment_intent.processing':
    // Payment submitted to bank
    break;
  case 'payment_intent.succeeded':
    // Funds received — fulfill the order
    break;
  case 'payment_intent.payment_failed':
    // Debit failed — notify the customer
    break;
  case 'charge.dispute.created':
    // SEPA chargeback — respond within deadline
    break;
}
```

**Expected result:** Your app handles the full SEPA lifecycle from processing to settlement or failure.

## Complete code example

File: `server-sepa.js`

```javascript
// server-sepa.js
// Node.js Express server for SEPA Direct Debit payments

const express = require('express');
const Stripe = require('stripe');
const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
const app = express();

app.use(express.static('public'));
app.use(express.json());

// Create a SEPA PaymentIntent
app.post('/api/create-sepa-intent', async (req, res) => {
  try {
    const customer = await stripe.customers.create({
      name: req.body.name,
      email: req.body.email
    });

    const intent = await stripe.paymentIntents.create({
      amount: req.body.amount,  // in cents
      currency: 'eur',
      customer: customer.id,
      payment_method_types: ['sepa_debit'],
      mandate_data: {
        customer_acceptance: {
          type: 'online',
          online: {
            ip_address: req.ip,
            user_agent: req.headers['user-agent']
          }
        }
      }
    });

    res.json({ client_secret: intent.client_secret });
  } catch (err) {
    res.status(400).json({ error: err.message });
  }
});

// Webhook handler for SEPA lifecycle events
app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(
      req.body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case 'payment_intent.processing':
      console.log('SEPA payment processing:', event.data.object.id);
      break;
    case 'payment_intent.succeeded':
      console.log('SEPA payment succeeded:', event.data.object.id);
      break;
    case 'payment_intent.payment_failed':
      console.log('SEPA payment failed:', event.data.object.id);
      break;
  }

  res.json({ received: true });
});

app.listen(3001, () => console.log('Server on port 3001'));
```

## Common mistakes

- **Using a currency other than EUR for SEPA Direct Debit** — undefined Fix: SEPA only supports EUR. Always set currency: 'eur' when creating a SEPA PaymentIntent.
- **Treating SEPA payments as instant like card payments** — undefined Fix: SEPA payments take 5-14 business days to settle. Use webhooks to track the status instead of relying on the initial response.
- **Not displaying the mandate text before the customer confirms** — undefined Fix: SEPA regulations require you to show mandate authorization text. Without it, chargebacks are nearly impossible to fight.
- **Fulfilling the order immediately after confirmation** — undefined Fix: Wait for the payment_intent.succeeded webhook before fulfilling. The 'processing' status means the bank has not confirmed the debit yet.

## Best practices

- Always display the SEPA mandate text next to the IBAN input for legal compliance
- Create a Stripe Customer before the PaymentIntent to enable reuse of the SEPA payment method
- Use webhooks to track SEPA payment status — do not poll the API
- Plan for chargebacks: SEPA allows disputes up to 8 weeks after the debit (13 months for unauthorized debits)
- Store the mandate ID returned by Stripe for your records
- If you need faster settlement, consider SEPA Instant Credit Transfer where available
- For teams building complex SEPA integrations, RapidDev can help architect the mandate and reconciliation flows

## Frequently asked questions

### How long does a SEPA Direct Debit payment take to settle?

SEPA payments typically take 5-14 business days to settle. The payment_intent.succeeded webhook fires when the funds are confirmed in your Stripe balance.

### Can customers dispute SEPA Direct Debit payments?

Yes. Customers can dispute authorized SEPA debits within 8 weeks, and unauthorized debits within 13 months. Always keep mandate records to defend against disputes.

### What test IBANs can I use with Stripe?

Use DE89370400440532013000 for a successful payment, DE62370400440532013001 for a failed payment, and DE35370400440532013002 for a payment that will be disputed.

### Do I need to be based in Europe to accept SEPA payments?

Your Stripe account needs to support EUR payouts, but you do not need to be based in a SEPA country. Check Stripe's country availability for your specific situation.

### Can I use SEPA for recurring subscriptions?

Yes. Set up the payment method with off_session mandate and use it for Stripe Subscriptions. The customer only provides their IBAN once, and subsequent charges are debited automatically.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-enable-sepa-direct-debit-in-stripe
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-enable-sepa-direct-debit-in-stripe
