Over the past year, Stripe fully retired the Sources API for local payment methods like SEPA Direct Debit, replacing it with the newer PaymentIntents + PaymentMethods model. If your WooCommerce store ever accepted SEPA Direct Debit before mid-2024, chances are you still have subscriptions pointing to old src_… IDs — and those are now failing silently or producing a “deprecated” error.
This post documents what happened, how we discovered it, how to identify affected customers, and how we fixed it safely across thousands of live subscriptions.
Table of Contents
⚠️ The Symptom
A subset of SEPA subscriptions suddenly started failing renewals.
Stripe’s dashboard showed this error message:
Using a SEPA Debit Source in the
sourcefield has been deprecated and is no longer a valid integration path.
WooCommerce, receiving a failed payment webhook, automatically cancelled those subscriptions — even though customers’ bank accounts were perfectly fine.
At first, it looked like a random Stripe connectivity issue. But after checking the metadata, every failing subscription had something in common:
_payment_method = stripe_sepa_debit
_stripe_source_id = src_XXXXXXXXXXXX
That was the clue: Stripe no longer accepts SEPA src_… Sources on PaymentIntents.
🧠 Root Cause: The Sources → PaymentMethods Migration
1. Stripe’s side
Historically, SEPA Direct Debit used the Sources API (src_… objects) to represent mandates and customer bank data.
In 2023–2024, Stripe migrated all local payment methods (SEPA, Bacs, iDEAL, etc.) to the PaymentMethods API, which uses pm_… objects tied to PaymentIntents.
To help merchants transition, Stripe introduced an internal, account-level migration called “Reusable Sources → PaymentMethods”, which creates corresponding pm_… entries for reusable Sources.
Any Source not migrated (e.g. missing a reusable mandate) becomes unusable.
🧾 References:
- Stripe Support: Reusable object migration from Sources to PaymentMethods
- Stripe Docs: Transitioning from Sources to PaymentMethods
- Stripe Support: Sources API error and migration guidance
2. WooCommerce’s side
WooCommerce’s Stripe Gateway plugin added an automatic “legacy SEPA repairer” to convert old subscriptions to the new model.
However, this repairer only targeted subscriptions using the old gateway ID stripe_sepa.
Many stores, including ours, had already switched to the new gateway ID (stripe_sepa_debit) before the Stripe migration — so those subscriptions weren’t picked up by the repairer.
They kept their old _stripe_source_id = src_… but were never updated to a pm_….
When those subs renewed, Woo tried to charge a PaymentIntent with a deprecated Source — and Stripe rejected it.
🧾 References:
- WooCommerce GitHub: woocommerce/woocommerce-gateway-stripe#3081 – SEPA migration discussion
- WooCommerce GitHub: PR #3249 – Update subscriptions sources to payment methods
- WooCommerce GitHub: #4636 – Migrating remaining src_ Source to pm_ for SEPA
🔎 Identifying Affected Users
Three quick SQL checks (read-only) revealed the scale of the issue:
1️⃣ Saved tokens still using src_…
SELECT COUNT(*)
FROM wp_woocommerce_payment_tokens
WHERE gateway_id = 'stripe_sepa'
AND token LIKE 'src_%';
2️⃣ Subscriptions storing a src_…
SELECT COUNT(*)
FROM wp_postmeta
WHERE meta_key = '_stripe_source_id'
AND meta_value LIKE 'src_%';
3️⃣ Split by gateway ID
SELECT pm.meta_value AS payment_method, COUNT(*) AS count
FROM wp_posts p
JOIN wp_postmeta pm ON pm.post_id = p.ID AND pm.meta_key = '_payment_method'
JOIN wp_postmeta src ON src.post_id = p.ID
WHERE p.post_type = 'shop_subscription'
AND src.meta_key = '_stripe_source_id'
AND src.meta_value LIKE 'src_%'
GROUP BY pm.meta_value;
This confirmed it:
- Only a handful of subscriptions used the legacy
stripe_sepa. - Tens of thousands used the new
stripe_sepa_debitbut still stored oldsrc_…IDs.
Those weren’t touched by the default migration.
🧩 Why the Default Migration Missed Them
- Enumeration gap:
Woo’s repairer runs only for subs where_payment_method = stripe_sepa.
Subs with_payment_method = stripe_sepa_debitbut_stripe_source_id = src_…were ignored. - Stripe account not fully migrated:
If Stripe hadn’t yet created matchingpm_…PaymentMethods for old Sources, Woo had nothing to swap in — it simply logged a note and moved on.
The result: thousands of subscriptions still referenced invalid src_… Sources, even though everything else looked fine in Woo.
🧰 The Fix — Step by Step
Step 1. Complete the Stripe Account Migration
First, the Reusable Sources → PaymentMethods migration must be completed on your Stripe account.
You can:
- Go to Stripe Dashboard → Settings → Payment methods and look for any “Migrate old Sources” notices, or
- Contact Stripe Support and request: “Please run the reusable Sources → PaymentMethods migration on our account for SEPA Direct Debit.”
After this runs, every reusable SEPA Source will gain a pm_… counterpart.
Step 2. Run or Extend WooCommerce’s Repair Job
Even after Stripe’s migration, Woo needs to update your local metadata.
You have two options:
Option A — Use the built-in repairer (for true legacy stripe_sepa subs)
Woo’s internal repair class:
WC_Stripe_Subscriptions_Repairer_Legacy_SEPA_Tokens
automatically updates subscriptions that use the legacy gateway.
If you have only those, re-running it from Woo → Status → Tools → Restart SEPA token update is enough.
Option B — Custom repair for all src_… (recommended)
We added a small mu-plugin to target any subscription with _stripe_source_id LIKE 'src_%', regardless of gateway id.
It calls the same safe updater Woo uses internally:
$updater = new WC_Stripe_Subscriptions_Legacy_SEPA_Token_Update();
$updater->maybe_update_subscription_source( $subscription );
You can run it from the Tools page or via WP-CLI in batches:
wp eval '
$u = new WC_Stripe_Subscriptions_Legacy_SEPA_Token_Update();
$ids = wc_get_orders([
"type" => "shop_subscription",
"status" => "any",
"return" => "ids",
"meta_query" => [[ "key" => "_stripe_source_id", "value" => "src_", "compare" => "LIKE" ]]
]);
foreach($ids as $id){ $s = wcs_get_subscription($id); if($s) $u->maybe_update_subscription_source($s); }
echo "Processed " . count($ids) . " subscriptions\n";
'
This safely updates every eligible sub to use a valid pm_… without touching customer mandates.
Step 3. Verify & Clean Up
After running the repairer, re-run your audit queries.
Any subscription still showing src_… now truly lacks a convertible mandate on Stripe — those customers must update their payment method manually.
WooCommerce lets you trigger that with the “Send update payment method email” action directly from the Subscriptions list.
Step 4. Prevent Future Issues
- Keep the new (UPE/standard) checkout enabled — it only ever creates
pm_…PaymentMethods. - Make sure “Saved payment methods” are allowed so subscriptions can reuse
pm_…safely. - Stay current with the WooCommerce Stripe Gateway plugin.
- Periodically run a quick check for lingering
src_…entries to ensure you’re fully clean.
⚙️ Bonus: Why You Can’t Reproduce This With New Users
The Sources API is now completely disabled for new SEPA objects — both in live and test mode.
If you try stripe sources create type=sepa_debit, Stripe returns:
The Sources API has reached end of life.
In order to continue processing your non-card payments,
you will need to migrate to the Payment Intents and Payment Methods API.
So, you can’t “recreate” a real src_… subscription anymore.
To simulate the bug locally, you can simply set _stripe_source_id = src_fake_test_123 in a test subscription — Woo will still try to pass it to Stripe and trigger the same deprecation error.
💡 Lessons Learned
- Stripe’s migration fixed the platform — not your database.
You still need to run Woo’s local repairer to update subscription metadata. - Don’t assume automatic repairers catch everything.
Many WooCommerce updates are scoped narrowly to prevent accidental data churn. Sometimes that means important objects are missed. - Bulk audits are your friend.
Quick SQL or WP-CLI reports can surface thousands of silent time bombs before customers notice. - Always test on staging first.
Use Stripe’s test mode with fake_stripe_source_idvalues to verify your fix flow without affecting real customers.
🏁 Results
After completing the Stripe account migration and running the enhanced repairer:
- All active subscriptions now use
pm_…SEPA PaymentMethods. - Renewals succeed through PaymentIntents with no
sourceparameter. - WooCommerce tokens and user payment methods are consistent.
- The “Sources API deprecated” errors have completely disappeared.
✅ In Summary
| Step | Action | Responsible |
|---|---|---|
| 1 | Complete “Reusable Sources → PaymentMethods” migration | Stripe Dashboard / Stripe Support |
| 2 | Update WooCommerce Stripe Gateway plugin, enable new checkout | Woo admin |
| 3 | Run repairer (built-in or custom) on all subs with _stripe_source_id LIKE 'src_%' | Woo admin |
| 4 | Audit remaining src_… and send “Update payment method” emails | Woo admin |
| 5 | Keep UPE checkout active and monitor logs | Ongoing |
🚀 Conclusion
The SEPA “Sources API deprecated” issue was a perfect example of a two-sided migration problem:
Stripe changed the underlying API, but local data in WooCommerce still pointed to legacy identifiers.
By:
- Completing the Stripe account-level migration, and
- Expanding WooCommerce’s repairer to include all
src_…references,
we fully modernized our SEPA Direct Debit subscriptions — without customer disruption, without touching sensitive data, and without downtime.
If your store still shows any _stripe_source_id = src_…, now’s the time to check before your next renewal wave.


