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
Enable SEPA Direct Debit in the Stripe Dashboard
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.
Create a PaymentIntent for SEPA on the server
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.
1// server.js2const express = require('express');3const Stripe = require('stripe');4const stripe = Stripe(process.env.STRIPE_SECRET_KEY);5const app = express();6app.use(express.json());78app.post('/api/create-sepa-intent', async (req, res) => {9 try {10 const intent = await stripe.paymentIntents.create({11 amount: req.body.amount, // in cents12 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});2930app.listen(3001);Expected result: The endpoint returns a client_secret for a SEPA PaymentIntent.
Collect the IBAN on the frontend
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.
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');89// Display mandate text to the customer10const 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.
Confirm the SEPA payment
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.
1const form = document.getElementById('sepa-form');2form.addEventListener('submit', async (e) => {3 e.preventDefault();45 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();1112 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').value20 }21 }22 }23 );2425 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.
Handle SEPA webhook events
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.
1// In your webhook handler:2switch (event.type) {3 case 'payment_intent.processing':4 // Payment submitted to bank5 break;6 case 'payment_intent.succeeded':7 // Funds received — fulfill the order8 break;9 case 'payment_intent.payment_failed':10 // Debit failed — notify the customer11 break;12 case 'charge.dispute.created':13 // SEPA chargeback — respond within deadline14 break;15}Expected result: Your app handles the full SEPA lifecycle from processing to settlement or failure.
Complete working example
1// server-sepa.js2// Node.js Express server for SEPA Direct Debit payments34const express = require('express');5const Stripe = require('stripe');6const stripe = Stripe(process.env.STRIPE_SECRET_KEY);7const app = express();89app.use(express.static('public'));10app.use(express.json());1112// Create a SEPA PaymentIntent13app.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.email18 });1920 const intent = await stripe.paymentIntents.create({21 amount: req.body.amount, // in cents22 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 });3536 res.json({ client_secret: intent.client_secret });37 } catch (err) {38 res.status(400).json({ error: err.message });39 }40});4142// Webhook handler for SEPA lifecycle events43app.post('/webhooks/stripe', express.raw({ type: 'application/json' }), (req, res) => {44 const sig = req.headers['stripe-signature'];45 let event;4647 try {48 event = stripe.webhooks.constructEvent(49 req.body,50 sig,51 process.env.STRIPE_WEBHOOK_SECRET52 );53 } catch (err) {54 return res.status(400).send(`Webhook Error: ${err.message}`);55 }5657 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 }6869 res.json({ received: true });70});7172app.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.
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.
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.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation