# How to cancel a subscription in Stripe

- Tool: Stripe
- Difficulty: Intermediate
- Time required: 10 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+
- Last updated: March 2026

## 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).

## Before you start

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

```
// No code needed — this is done in the Stripe Dashboard:
// 1. Go to Dashboard → Subscriptions
// 2. Find and click the subscription
// 3. Click ••• → Cancel subscription
// 4. Choose 'Immediately' or 'At end of current period'
// 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.

```
// 1. Enable cancellation in Dashboard → Settings → Customer portal
// 2. Redirect the customer to the portal:

app.post('/create-portal-session', async (req, res) => {
  const portalSession = await stripe.billingPortal.sessions.create({
    customer: req.body.customerId,
    return_url: 'https://yoursite.com/account',
  });
  res.json({ url: portalSession.url });
});
```

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

```
// Immediate cancellation
const canceledSubscription = await stripe.subscriptions.cancel(
  'sub_xxx' // Subscription ID
);

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

```
// Cancel at end of billing period
const subscription = await stripe.subscriptions.update('sub_xxx', {
  cancel_at_period_end: true,
});

console.log(subscription.cancel_at_period_end); // true
console.log(subscription.current_period_end);   // Unix timestamp when it will cancel

// To undo the cancellation before period ends:
const reactivated = await stripe.subscriptions.update('sub_xxx', {
  cancel_at_period_end: false,
});
```

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

```
// In your webhook handler:
case 'customer.subscription.deleted': {
  const subscription = event.data.object;
  console.log('Subscription canceled:', subscription.id);
  console.log('Customer:', subscription.customer);
  // Revoke access for this customer
  await revokeAccess(subscription.customer);
  break;
}

case 'customer.subscription.updated': {
  const subscription = event.data.object;
  if (subscription.cancel_at_period_end) {
    console.log('Subscription scheduled to cancel:', subscription.id);
    // Optionally show "your plan will end on [date]" in your UI
  }
  break;
}
```

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

## Complete code example

File: `cancel-subscription.js`

```javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const app = express();

app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case 'customer.subscription.deleted':
      console.log('Access revoked for:', event.data.object.customer);
      break;
    case 'customer.subscription.updated':
      if (event.data.object.cancel_at_period_end) {
        console.log('Cancellation scheduled:', event.data.object.id);
      }
      break;
  }
  res.json({ received: true });
});

app.use(express.json());

// Cancel immediately
app.post('/cancel-subscription', async (req, res) => {
  const { subscriptionId } = req.body;
  const canceled = await stripe.subscriptions.cancel(subscriptionId);
  res.json({ status: canceled.status });
});

// Cancel at period end
app.post('/cancel-subscription-at-period-end', async (req, res) => {
  const { subscriptionId } = req.body;
  const updated = await stripe.subscriptions.update(subscriptionId, {
    cancel_at_period_end: true,
  });
  res.json({
    cancel_at_period_end: updated.cancel_at_period_end,
    period_end: new Date(updated.current_period_end * 1000).toISOString(),
  });
});

// Undo scheduled cancellation
app.post('/reactivate-subscription', async (req, res) => {
  const { subscriptionId } = req.body;
  const updated = await stripe.subscriptions.update(subscriptionId, {
    cancel_at_period_end: false,
  });
  res.json({ cancel_at_period_end: updated.cancel_at_period_end });
});

// Customer Portal
app.post('/create-portal-session', async (req, res) => {
  const portal = await stripe.billingPortal.sessions.create({
    customer: req.body.customerId,
    return_url: `${req.headers.origin}/account`,
  });
  res.json({ url: portal.url });
});

app.listen(4000, () => console.log('Server on port 4000'));
```

## Common mistakes

- **Revoking access immediately when cancel_at_period_end is set** — undefined Fix: cancel_at_period_end means the customer has access until the period ends. Only revoke access when you receive the customer.subscription.deleted webhook.
- **Not offering cancel-at-period-end as an option** — undefined Fix: Most customers expect to keep access until the end of their paid period. Immediate cancellation can feel like losing value they paid for.
- **Not listening for cancellation webhooks** — undefined Fix: Always handle customer.subscription.deleted to revoke access. Don't rely on API responses alone — the actual cancellation may happen later (at period end).
- **Forgetting that cancel_at_period_end is reversible** — undefined Fix: 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

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

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-cancel-a-subscription-in-stripe
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-cancel-a-subscription-in-stripe
