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

How to cancel a subscription in Stripe

Cancel a Stripe subscription through three methods: the Stripe Dashboard (manual), the Customer Portal (self-service), or the API (programmatic). You can cancel immediately or at the end of the current billing period using cancel_at_period_end. Always listen for the customer.subscription.deleted webhook to revoke access.

What you'll learn

  • How to cancel subscriptions via Dashboard, Customer Portal, and API
  • The difference between immediate cancellation and cancel-at-period-end
  • How to handle the cancellation webhook to revoke access
  • How to offer pause or downgrade alternatives to cancellation
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate5 min read10 minutesStripe API v2024-12+, Node.js 18+March 2026RapidDev Engineering Team
TL;DR

Cancel a Stripe subscription through three methods: the Stripe Dashboard (manual), the Customer Portal (self-service), or the API (programmatic). You can cancel immediately or at the end of the current billing period using cancel_at_period_end. Always listen for the customer.subscription.deleted webhook to revoke access.

Three Ways to Cancel a Stripe Subscription

Stripe provides three cancellation methods depending on your needs. The Dashboard lets you cancel manually. The Customer Portal gives subscribers self-service cancellation (reducing your support load). The API lets you build custom cancellation flows with retention offers or surveys. The most important choice is whether to cancel immediately (stops billing and access now) or at the end of the current period (lets the customer use their remaining paid time).

Prerequisites

  • An active Stripe subscription to cancel
  • Access to the Stripe Dashboard for manual cancellation
  • Node.js 18+ with the stripe package for API cancellation
  • Customer Portal configured for self-service cancellation

Step-by-step guide

1

Cancel via the Stripe Dashboard

For manual, one-off cancellations: go to the Stripe Dashboard → Subscriptions → find the subscription → click the three-dot menu → Cancel subscription. Choose 'Immediately' or 'At end of period'.

typescript
1// No code needed — this is done in the Stripe Dashboard:
2// 1. Go to Dashboard → Subscriptions
3// 2. Find and click the subscription
4// 3. Click ••• → Cancel subscription
5// 4. Choose 'Immediately' or 'At end of current period'
6// 5. Click Cancel subscription

Expected result: The subscription is canceled. If 'at end of period', it stays active until the period ends. If 'immediately', it ends now.

2

Cancel via the Customer Portal (self-service)

Enable cancellation in the Customer Portal settings and redirect subscribers there. They can cancel without contacting you.

typescript
1// 1. Enable cancellation in Dashboard → Settings → Customer portal
2// 2. Redirect the customer to the portal:
3
4app.post('/create-portal-session', async (req, res) => {
5 const portalSession = await stripe.billingPortal.sessions.create({
6 customer: req.body.customerId,
7 return_url: 'https://yoursite.com/account',
8 });
9 res.json({ url: portalSession.url });
10});

Expected result: The customer is redirected to the Stripe Customer Portal where they can cancel their subscription.

3

Cancel via the API — immediately

Call stripe.subscriptions.cancel() to cancel immediately. The subscription status changes to 'canceled' and billing stops.

typescript
1// Immediate cancellation
2const canceledSubscription = await stripe.subscriptions.cancel(
3 'sub_xxx' // Subscription ID
4);
5
6console.log(canceledSubscription.status); // 'canceled'

Expected result: The subscription is immediately canceled. No more invoices are generated.

4

Cancel via the API — at period end

Set cancel_at_period_end to true. The subscription remains active until the current billing period ends, then cancels automatically.

typescript
1// Cancel at end of billing period
2const subscription = await stripe.subscriptions.update('sub_xxx', {
3 cancel_at_period_end: true,
4});
5
6console.log(subscription.cancel_at_period_end); // true
7console.log(subscription.current_period_end); // Unix timestamp when it will cancel
8
9// To undo the cancellation before period ends:
10const reactivated = await stripe.subscriptions.update('sub_xxx', {
11 cancel_at_period_end: false,
12});

Expected result: The subscription is marked for cancellation. It stays active and functional until the period ends.

5

Handle the cancellation webhook

Listen for customer.subscription.deleted to know when the subscription is fully canceled and revoke access.

typescript
1// In your webhook handler:
2case 'customer.subscription.deleted': {
3 const subscription = event.data.object;
4 console.log('Subscription canceled:', subscription.id);
5 console.log('Customer:', subscription.customer);
6 // Revoke access for this customer
7 await revokeAccess(subscription.customer);
8 break;
9}
10
11case 'customer.subscription.updated': {
12 const subscription = event.data.object;
13 if (subscription.cancel_at_period_end) {
14 console.log('Subscription scheduled to cancel:', subscription.id);
15 // Optionally show "your plan will end on [date]" in your UI
16 }
17 break;
18}

Expected result: Your server revokes access when the subscription is fully canceled, and optionally shows cancellation status in the UI.

Complete working example

cancel-subscription.js
1const express = require('express');
2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
3
4const app = express();
5
6app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
7 const sig = req.headers['stripe-signature'];
8 let event;
9 try {
10 event = stripe.webhooks.constructEvent(
11 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
12 );
13 } catch (err) {
14 return res.status(400).send(`Webhook Error: ${err.message}`);
15 }
16
17 switch (event.type) {
18 case 'customer.subscription.deleted':
19 console.log('Access revoked for:', event.data.object.customer);
20 break;
21 case 'customer.subscription.updated':
22 if (event.data.object.cancel_at_period_end) {
23 console.log('Cancellation scheduled:', event.data.object.id);
24 }
25 break;
26 }
27 res.json({ received: true });
28});
29
30app.use(express.json());
31
32// Cancel immediately
33app.post('/cancel-subscription', async (req, res) => {
34 const { subscriptionId } = req.body;
35 const canceled = await stripe.subscriptions.cancel(subscriptionId);
36 res.json({ status: canceled.status });
37});
38
39// Cancel at period end
40app.post('/cancel-subscription-at-period-end', async (req, res) => {
41 const { subscriptionId } = req.body;
42 const updated = await stripe.subscriptions.update(subscriptionId, {
43 cancel_at_period_end: true,
44 });
45 res.json({
46 cancel_at_period_end: updated.cancel_at_period_end,
47 period_end: new Date(updated.current_period_end * 1000).toISOString(),
48 });
49});
50
51// Undo scheduled cancellation
52app.post('/reactivate-subscription', async (req, res) => {
53 const { subscriptionId } = req.body;
54 const updated = await stripe.subscriptions.update(subscriptionId, {
55 cancel_at_period_end: false,
56 });
57 res.json({ cancel_at_period_end: updated.cancel_at_period_end });
58});
59
60// Customer Portal
61app.post('/create-portal-session', async (req, res) => {
62 const portal = await stripe.billingPortal.sessions.create({
63 customer: req.body.customerId,
64 return_url: `${req.headers.origin}/account`,
65 });
66 res.json({ url: portal.url });
67});
68
69app.listen(4000, () => console.log('Server on port 4000'));

Common mistakes when canceling a subscription in Stripe

Why it's a problem: Revoking access immediately when cancel_at_period_end is set

How to avoid: cancel_at_period_end means the customer has access until the period ends. Only revoke access when you receive the customer.subscription.deleted webhook.

Why it's a problem: Not offering cancel-at-period-end as an option

How to avoid: Most customers expect to keep access until the end of their paid period. Immediate cancellation can feel like losing value they paid for.

Why it's a problem: Not listening for cancellation webhooks

How to avoid: Always handle customer.subscription.deleted to revoke access. Don't rely on API responses alone — the actual cancellation may happen later (at period end).

Why it's a problem: Forgetting that cancel_at_period_end is reversible

How to avoid: The customer might want to undo their cancellation. Provide a 'reactivate' option that sets cancel_at_period_end: false before the period ends.

Best practices

  • Default to cancel_at_period_end: true so customers keep access for the time they paid for
  • Enable the Customer Portal for self-service cancellation to reduce support requests
  • Always listen for customer.subscription.deleted webhook to revoke access
  • Offer a reactivation option for customers who canceled at period end but change their mind
  • Consider adding a cancellation survey in the Customer Portal settings to understand churn
  • Before canceling, offer alternatives like downgrading to a cheaper plan or pausing

Still stuck?

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

ChatGPT Prompt

Write Node.js Express endpoints to cancel a Stripe subscription immediately, cancel at period end, and reactivate a scheduled cancellation. Include webhook handling for customer.subscription.deleted to revoke access.

Stripe Prompt

Add subscription cancellation to my app. Create three endpoints: 1) immediate cancel, 2) cancel at period end, 3) reactivate/undo cancellation. Also add a webhook handler for customer.subscription.deleted. And add a Customer Portal redirect endpoint.

Frequently asked questions

Does the customer get a refund when they cancel?

Not automatically. Immediate cancellation does not refund the current period. If you want to refund, create a refund separately via stripe.refunds.create(). Cancel-at-period-end lets them use the remaining time.

Can I cancel a subscription that's in a trial?

Yes. Canceling during a trial ends it immediately (or at trial end if using cancel_at_period_end). No charge is made since the trial had not converted to paid.

What's the difference between 'canceled' and 'unpaid' status?

'Canceled' means the subscription was explicitly canceled. 'Unpaid' means all payment retry attempts failed and the subscription was deactivated. Both result in no further billing.

Can I add a cancellation survey?

Yes. In the Customer Portal settings (Dashboard → Settings → Customer portal → Cancellations), you can enable a cancellation reason survey with customizable options.

What if I need help reducing churn?

RapidDev can help build custom cancellation flows with retention offers, pause options, downgrade suggestions, and win-back campaigns that integrate with your Stripe subscription system.

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.