stripebilling portaldunninginvoluntary churn

Stripe Can Now Recover an Expired Billing Portal Session. Half of Email Clicks Land After the Old Link Would Have Died.

Stripe's new after_expiration setting lets a customer recover an expired portal link by verifying their email — instead of hitting a dead end.

XY
5 October 2026 · 8 min read

Every dunning email, renewal reminder, and "update your card" notice you send carries a link into Stripe's hosted billing portal. That link is a session, and Stripe's own API reference is explicit about what kind: a url field described as "the short-lived URL of the session that gives customers access to the customer portal." Short-lived is the operative phrase. It's fine if the customer clicks within the window. It's a dead end if they don't — and until September 30, a dead end is exactly what it stayed.

Key stat
~50%
Share of email campaign clicks that land more than 3 hours after the message was sent
Source: GetResponse analysis of 5.5 billion emails, reported by Marketing Charts

That number isn't about dunning specifically — it's a general email-engagement study — but it describes exactly the gap a portal session's short lifespan was built into. A link that's only good for a narrow window after it's generated is a link designed around the assumption that people click fast. Half the time, in ordinary email behavior, they don't.

What actually shipped on September 30

Stripe added a new parameter, after_expiration, to the Billing Portal Sessions API. You set it when creating a session, and it controls what the customer sees once that session's URL has expired. Setting after_expiration.type to customer_login does the actual work: the customer who clicks the dead link is prompted to verify their email address, and Stripe issues them a brand-new session — same configuration, same feature set, same branding — on the spot. No new link to request from you, no round trip back through your app to regenerate one.

The nested customer_login object takes one more optional field: expires_at, a timestamp that caps how long the recovery option itself stays open. Leave it unset and the customer can recover the session whenever they eventually click, days or months later. Set it and recovery closes on your schedule instead — useful if an old dunning email sitting unread in an inbox for half a year is a risk profile you'd rather not carry indefinitely.

BehaviorBefore September 30With after_expiration = customer_login
Customer clicks an expired linkDead end — generic expired-session statePrompted to verify email, then dropped into a fresh session
Who has to act to fix itThe customer has to find their way back into your app for a new link, or you have to resend oneNobody — recovery happens on the expired link itself
Session configuration after recoveryN/A — session is goneIdentical to the original session (features, branding, flow)
How long recovery stays availableN/AIndefinite by default, or capped with expires_at

Why this is a dunning problem, not just a UX nicety

Our own guide to Stripe dunning makes the case that every recovery email should link straight into the customer portal, because friction in the update flow is a silent recovery killer. That advice assumed the customer clicks while the link is still alive. For a subscriber who opens a failed-payment email on day one, that's a safe assumption. For the subscriber who opens it on day four, after the second or third touchpoint in a 21–28 day retry sequence, the original link from email one is long dead — and if they go digging back through their inbox for it instead of clicking the latest one, they'd have hit exactly the dead end this fixes.

It compounds with a problem we covered in dunning email deliverability: a payment-failed notice doesn't even have to be read the moment it lands to still be the email that saves the subscription. Someone scanning a crowded inbox, seeing "payment failed," and deciding to deal with it "later today" is a completely normal response — and later today, by itself, used to be enough to turn a perfectly good recovery email into a broken one.

When email campaign clicks actually happen, by time since send
Within 1 hour34%
Within 3 hours50%
Within 24 hours81%
After 24 hours19%

Source: GetResponse analysis of 5.5 billion emails (July 2019–June 2020), reported by Marketing Charts.

Read the bottom row again: roughly one in five clicks on an ordinary email campaign lands more than a full day after it was sent. Dunning and renewal-reminder emails aren't ordinary marketing campaigns, but they're opened by the same humans, on the same inboxes, with the same habit of triaging rather than acting instantly. Every one of those late clicks, on any link that's "short-lived" by design, was a coin flip between a working session and a dead one. after_expiration takes that coin flip off the table.

Where it fits next to the other portal changes this year

This isn't the first time in 2026 Stripe has touched what a portal link does once a customer actually clicks it. Back in August, Stripe shipped a customer_update deep link so a dunning email could route a customer straight to fixing a stale billing address instead of dumping everyone on the generic "update your card" page — something we broke down in our look at that release. That change was about sending the customer to the right page. This one is about making sure there's a working page to send them to at all, no matter how late they arrive. Together they close two separate, equally common ways a technically-correct dunning email still fails to recover a payment.

It also slots directly into the cadence most teams already run. Our guide to pre-renewal reminders recommends a reminder well before a charge ever hits — exactly the kind of email that sits unread for days, because there's no failure yet to create urgency. A renewal reminder sent a week out, with a portal link a customer doesn't click until three days before their card is charged, is squarely inside the failure mode this fixes. Set after_expiration on reminder-sequence sessions and that entire class of "the customer meant to act but the link was already dead" failure disappears.

Wiring it in

It's an additive parameter on session creation, not a rebuild of your portal integration:

  • Use API version 2026-09-30.preview (or later) when creating the session — this is a preview-channel feature, not yet in the GA channel.
  • Pass after_expiration[type]=customer_login on POST /v1/billing_portal/sessions.
  • Optionally set after_expiration[customer_login][expires_at] if you want recovery itself to have a deadline rather than staying open indefinitely.
  • Do this for every session you generate for a dunning, renewal-reminder, or card-update email — anywhere the gap between "link generated" and "link clicked" isn't guaranteed to be short.

There's no webhook change and no new object to track. The session you create still returns the same url field your email template already uses — the only difference is what happens on Stripe's side if the customer is late.

What it doesn't fix

It doesn't fix a card that's actually expired, a tax ID that's wrong, or a subscriber who's decided for real reasons that they're done — none of which is a session-expiration problem to begin with. It's also preview-only today, which means it's worth testing against a sandbox before you depend on it in production, and Stripe could still adjust the behavior before it reaches general availability. And it only helps if you're actually setting it: an existing integration that never touches after_expiration gets the old dead-end behavior by default, preview API version or not.

What it does fix is narrow and, for exactly that reason, cheap to ship: a customer who intended to update their payment method, clicked the link you sent them, and got nothing for their trouble. That's a worse outcome than a bounced email, because the customer did the thing you asked and still failed. Run your current recovery rate through our churn calculator before and after turning this on for your dunning sequence — a meaningful share of "the customer never responded" failures in most accounts are actually "the customer responded four days too late for the link to still work," and that's the exact gap this closes. For the subscribers who click, read, and still decide to leave, that's a different problem — the one CancelFlow is built to catch at the cancel page itself, once a customer has made it that far on purpose rather than by accident.

Frequently asked questions

What is Stripe's after_expiration parameter on billing portal sessions?+

It's a new parameter on the Billing Portal Sessions API, shipped September 30, 2026 in the 2026-09-30.preview API version. You set it when you create a portal session, and it controls what happens after that session's short-lived URL expires. Setting after_expiration.type to customer_login lets the customer who holds the expired link verify their email address and receive a replacement session with the same configuration, instead of landing on a dead page.

Does the customer need a new link from me, or does the old one just start working again?+

Neither, exactly. The original URL stays expired — what changes is what the customer sees when they click it. Instead of a dead end, they're prompted to verify the email address tied to that session, and Stripe issues a fresh session on the spot with the same portal configuration (same feature set, same branding) the original session had. You never have to regenerate or resend anything.

Is this feature generally available, or still in preview?+

Preview only, as of the September 30, 2026 Endive release. It lives under the 2026-09-30.preview API version, alongside other not-yet-GA changes like Trial Offers' archiving settings. You'll need to opt your API calls into the preview version to use after_expiration today, and Stripe can still change preview behavior before it reaches general availability.

Can I set a deadline on how long the recovery option itself stays open?+

Yes. The customer_login object inside after_expiration accepts an optional expires_at timestamp. Set it if you want recovery to close after, say, 14 days — useful if a stale link sitting in someone's inbox for months is a security concern for your business. Leave it out and the customer can recover the session at any time after the original one expires.

Try CancelFlow

Stop losing subscribers today

One script tag. One function call. A live cancellation flow in under 10 minutes.

Start free trial →
← All postsHome