Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to enable SEPA Direct Debit in Stripe

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.

What you'll learn

  • How to activate SEPA Direct Debit in the Stripe Dashboard
  • How to collect IBAN details and mandate acceptance
  • How to create and confirm a SEPA PaymentIntent
  • How to handle SEPA-specific webhook events and settlement delays
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate6 min read30 minutesStripe API 2024-12+, Node.js 18+, SEPA zone (EUR currency)March 2026RapidDev Engineering Team
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.

Prerequisites

  • 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.

typescript
1// server.js
2const express = require('express');
3const Stripe = require('stripe');
4const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
5const app = express();
6app.use(express.json());
7
8app.post('/api/create-sepa-intent', async (req, res) => {
9 try {
10 const intent = await stripe.paymentIntents.create({
11 amount: req.body.amount, // in cents
12 currency: 'eur',
13 payment_method_types: ['sepa_debit'],
14 mandate_data: {
15 customer_acceptance: {
16 type: 'online',
17 online: {
18 ip_address: req.ip,
19 user_agent: req.headers['user-agent']
20 }
21 }
22 }
23 });
24 res.json({ client_secret: intent.client_secret });
25 } catch (err) {
26 res.status(400).json({ error: err.message });
27 }
28});
29
30app.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.

typescript
1const stripe = Stripe('pk_test_your_publishable_key');
2const elements = stripe.elements();
3const ibanElement = elements.create('iban', {
4 supportedCountries: ['SEPA'],
5 placeholderCountry: 'DE'
6});
7ibanElement.mount('#iban-element');
8
9// Display mandate text to the customer
10const mandateText = document.getElementById('mandate-text');
11mandateText.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.

typescript
1const form = document.getElementById('sepa-form');
2form.addEventListener('submit', async (e) => {
3 e.preventDefault();
4
5 const res = await fetch('/api/create-sepa-intent', {
6 method: 'POST',
7 headers: { 'Content-Type': 'application/json' },
8 body: JSON.stringify({ amount: 1000 })
9 });
10 const { client_secret } = await res.json();
11
12 const { error, paymentIntent } = await stripe.confirmSepaDebitPayment(
13 client_secret,
14 {
15 payment_method: {
16 sepa_debit: { iban: ibanElement },
17 billing_details: {
18 name: document.getElementById('name').value,
19 email: document.getElementById('email').value
20 }
21 }
22 }
23 );
24
25 if (error) {
26 console.error(error.message);
27 } else {
28 // paymentIntent.status will be 'processing'
29 console.log('Payment submitted:', paymentIntent.id);
30 }
31});

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.

typescript
1// In your webhook handler:
2switch (event.type) {
3 case 'payment_intent.processing':
4 // Payment submitted to bank
5 break;
6 case 'payment_intent.succeeded':
7 // Funds received — fulfill the order
8 break;
9 case 'payment_intent.payment_failed':
10 // Debit failed — notify the customer
11 break;
12 case 'charge.dispute.created':
13 // SEPA chargeback — respond within deadline
14 break;
15}

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

Complete working example

server-sepa.js
1// server-sepa.js
2// Node.js Express server for SEPA Direct Debit payments
3
4const express = require('express');
5const Stripe = require('stripe');
6const stripe = Stripe(process.env.STRIPE_SECRET_KEY);
7const app = express();
8
9app.use(express.static('public'));
10app.use(express.json());
11
12// Create a SEPA PaymentIntent
13app.post('/api/create-sepa-intent', async (req, res) => {
14 try {
15 const customer = await stripe.customers.create({
16 name: req.body.name,
17 email: req.body.email
18 });
19
20 const intent = await stripe.paymentIntents.create({
21 amount: req.body.amount, // in cents
22 currency: 'eur',
23 customer: customer.id,
24 payment_method_types: ['sepa_debit'],
25 mandate_data: {
26 customer_acceptance: {
27 type: 'online',
28 online: {
29 ip_address: req.ip,
30 user_agent: req.headers['user-agent']
31 }
32 }
33 }
34 });
35
36 res.json({ client_secret: intent.client_secret });
37 } catch (err) {
38 res.status(400).json({ error: err.message });
39 }
40});
41
42// Webhook handler for SEPA lifecycle events
43app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {
44 const sig = req.headers['stripe-signature'];
45 let event;
46
47 try {
48 event = stripe.webhooks.constructEvent(
49 req.body,
50 sig,
51 process.env.STRIPE_WEBHOOK_SECRET
52 );
53 } catch (err) {
54 return res.status(400).send(`Webhook Error: ${err.message}`);
55 }
56
57 switch (event.type) {
58 case 'payment_intent.processing':
59 console.log('SEPA payment processing:', event.data.object.id);
60 break;
61 case 'payment_intent.succeeded':
62 console.log('SEPA payment succeeded:', event.data.object.id);
63 break;
64 case 'payment_intent.payment_failed':
65 console.log('SEPA payment failed:', event.data.object.id);
66 break;
67 }
68
69 res.json({ received: true });
70});
71
72app.listen(3001, () => console.log('Server on port 3001'));

Common mistakes when enabling SEPA Direct Debit in Stripe

Why it's a problem: Using a currency other than EUR for SEPA Direct Debit

How to avoid: SEPA only supports EUR. Always set currency: 'eur' when creating a SEPA PaymentIntent.

Why it's a problem: Treating SEPA payments as instant like card payments

How to avoid: SEPA payments take 5-14 business days to settle. Use webhooks to track the status instead of relying on the initial response.

Why it's a problem: Not displaying the mandate text before the customer confirms

How to avoid: SEPA regulations require you to show mandate authorization text. Without it, chargebacks are nearly impossible to fight.

Why it's a problem: Fulfilling the order immediately after confirmation

How to avoid: 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

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

I need to accept SEPA Direct Debit payments with Stripe. Show me how to create a PaymentIntent with sepa_debit payment method type, collect an IBAN with the Stripe IBAN Element, display mandate text, confirm the payment, and handle the asynchronous webhook events for processing, succeeded, and failed states.

Stripe Prompt

Create a Node.js Express endpoint that creates a SEPA Direct Debit PaymentIntent with mandate_data. Then show the frontend code to mount a Stripe IBAN Element, display the mandate acceptance text, and call stripe.confirmSepaDebitPayment with billing details.

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.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.