/stripe-guides

How to cancel a subscription in Stripe?

Learn how to cancel a subscription in Stripe using the Dashboard, API, or Customer Portal. Step-by-step instructions for each method. Stay in sync with webhooks.

Matt Graham, CEO of Rapid Developers

Book a call with an Expert

Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.

Book a free consultation

How to cancel a subscription in Stripe?

How to Cancel a Subscription in Stripe

 

Introduction

 

Canceling a subscription in Stripe can be done through either the Stripe Dashboard or programmatically via the Stripe API. This tutorial covers both methods in detail, providing step-by-step instructions for each approach.

 

Method 1: Canceling a Subscription via the Stripe Dashboard

 

Step 1: Log in to your Stripe Dashboard

 

Navigate to https://dashboard.stripe.com/ and log in with your credentials.

 

Step 2: Access the Customers section

 

In the left sidebar, click on "Customers" to view all your customers.

 

Step 3: Find the customer with the subscription you want to cancel

 

Use the search function or browse through your customer list to find the specific customer.

 

Step 4: View customer details

 

Click on the customer's name to access their detailed profile.

 

Step 5: Locate the subscription

 

Scroll down to the "Subscriptions" section where all active subscriptions for this customer are listed.

 

Step 6: Cancel the subscription

 

  • Click on the specific subscription you want to cancel
  • Click the "Cancel subscription" button (usually located in the top-right corner)
  • A dialog box will appear asking for confirmation and offering options

 

Step 7: Choose cancellation options

 

You'll have two main options:

  • Cancel at period end: The subscription will remain active until the end of the current billing period
  • Cancel immediately: The subscription will end right away

 

Step 8: Confirm cancellation

 

Click the "Cancel subscription" button in the dialog to finalize the cancellation.

 

Method 2: Canceling a Subscription Programmatically via the Stripe API

 

Step 1: Set up your development environment

 

Ensure you have the Stripe library installed for your programming language. Here are installation commands for popular languages:

For Node.js:

npm install stripe

For Python:

pip install stripe

For PHP:

composer require stripe/stripe-php

For Ruby:

gem install stripe

 

Step 2: Initialize the Stripe client

 

Node.js:

const stripe = require('stripe')('sk_test_your_secret_key');
// Replace 'sk_test_your_secret_key' with your actual secret key

Python:

import stripe
stripe.api_key = "sk_test_your_secret\_key"
# Replace 'sk_test_your_secret_key' with your actual secret key

PHP:

\Stripe\Stripe::setApiKey('sk_test_your_secret_key');
// Replace 'sk_test_your_secret_key' with your actual secret key

Ruby:

require 'stripe'
Stripe.api_key = 'sk_test_your_secret\_key'
# Replace 'sk_test_your_secret_key' with your actual secret key

 

Step 3: Cancel the subscription by ID

 

Option A: Cancel immediately

 

Node.js:

async function cancelSubscription(subscriptionId) {
  try {
    const subscription = await stripe.subscriptions.del(subscriptionId);
    console.log('Subscription canceled successfully:', subscription.id);
    return subscription;
  } catch (error) {
    console.error('Error canceling subscription:', error);
    throw error;
  }
}

// Usage
cancelSubscription('sub\_12345');

Python:

def cancel_subscription(subscription_id):
    try:
        subscription = stripe.Subscription.delete(subscription\_id)
        print(f"Subscription canceled successfully: {subscription.id}")
        return subscription
    except Exception as e:
        print(f"Error canceling subscription: {str(e)}")
        raise e

# Usage
cancel_subscription('sub_12345')

PHP:

function cancelSubscription($subscriptionId) {
    try {
        $subscription = \Stripe\Subscription::retrieve($subscriptionId);
        $canceledSubscription = $subscription->cancel();
        echo "Subscription canceled successfully: " . $canceledSubscription->id;
        return $canceledSubscription;
    } catch (\Exception $e) {
        echo "Error canceling subscription: " . $e->getMessage();
        throw $e;
    }
}

// Usage
cancelSubscription('sub\_12345');

Ruby:

def cancel_subscription(subscription_id)
  begin
    subscription = Stripe::Subscription.delete(subscription\_id)
    puts "Subscription canceled successfully: #{subscription.id}"
    return subscription
  rescue Stripe::StripeError => e
    puts "Error canceling subscription: #{e.message}"
    raise e
  end
end

# Usage
cancel_subscription('sub_12345')

 

Option B: Cancel at period end

 

Node.js:

async function cancelSubscriptionAtPeriodEnd(subscriptionId) {
  try {
    const subscription = await stripe.subscriptions.update(subscriptionId, {
      cancel_at_period\_end: true
    });
    console.log('Subscription set to cancel at period end:', subscription.id);
    return subscription;
  } catch (error) {
    console.error('Error updating subscription:', error);
    throw error;
  }
}

// Usage
cancelSubscriptionAtPeriodEnd('sub\_12345');

Python:

def cancel_subscription_at_period_end(subscription\_id):
    try:
        subscription = stripe.Subscription.modify(
            subscription\_id,
            cancel_at_period\_end=True
        )
        print(f"Subscription set to cancel at period end: {subscription.id}")
        return subscription
    except Exception as e:
        print(f"Error updating subscription: {str(e)}")
        raise e

# Usage
cancel_subscription_at_period_end('sub\_12345')

PHP:

function cancelSubscriptionAtPeriodEnd($subscriptionId) {
    try {
        $subscription = \Stripe\Subscription::retrieve($subscriptionId);
        $updatedSubscription = $subscription->update([
            'cancel_at_period\_end' => true
        ]);
        echo "Subscription set to cancel at period end: " . $updatedSubscription->id;
        return $updatedSubscription;
    } catch (\Exception $e) {
        echo "Error updating subscription: " . $e->getMessage();
        throw $e;
    }
}

// Usage
cancelSubscriptionAtPeriodEnd('sub\_12345');

Ruby:

def cancel_subscription_at_period_end(subscription\_id)
  begin
    subscription = Stripe::Subscription.update(
      subscription\_id,
      { cancel_at_period\_end: true }
    )
    puts "Subscription set to cancel at period end: #{subscription.id}"
    return subscription
  rescue Stripe::StripeError => e
    puts "Error updating subscription: #{e.message}"
    raise e
  end
end

# Usage
cancel_subscription_at_period_end('sub\_12345')

 

Step 4: Verify the cancellation

 

Node.js:

async function verifySubscriptionStatus(subscriptionId) {
  try {
    const subscription = await stripe.subscriptions.retrieve(subscriptionId);
    console.log('Subscription status:', subscription.status);
    console.log('Cancel at period end:', subscription.cancel_at_period\_end);
    return subscription;
  } catch (error) {
    console.error('Error retrieving subscription:', error);
    throw error;
  }
}

// Usage
verifySubscriptionStatus('sub\_12345');

Python:

def verify_subscription_status(subscription\_id):
    try:
        subscription = stripe.Subscription.retrieve(subscription\_id)
        print(f"Subscription status: {subscription.status}")
        print(f"Cancel at period end: {subscription.cancel_at_period\_end}")
        return subscription
    except Exception as e:
        print(f"Error retrieving subscription: {str(e)}")
        raise e

# Usage
verify_subscription_status('sub\_12345')

PHP:

function verifySubscriptionStatus($subscriptionId) {
    try {
        $subscription = \Stripe\Subscription::retrieve($subscriptionId);
        echo "Subscription status: " . $subscription->status . "\n";
        echo "Cancel at period end: " . ($subscription->cancel_at_period\_end ? 'Yes' : 'No');
        return $subscription;
    } catch (\Exception $e) {
        echo "Error retrieving subscription: " . $e->getMessage();
        throw $e;
    }
}

// Usage
verifySubscriptionStatus('sub\_12345');

Ruby:

def verify_subscription_status(subscription\_id)
  begin
    subscription = Stripe::Subscription.retrieve(subscription\_id)
    puts "Subscription status: #{subscription.status}"
    puts "Cancel at period end: #{subscription.cancel_at_period\_end}"
    return subscription
  rescue Stripe::StripeError => e
    puts "Error retrieving subscription: #{e.message}"
    raise e
  end
end

# Usage
verify_subscription_status('sub\_12345')

 

Method 3: Canceling a Subscription Using Stripe Checkout

 

If you're using Stripe Checkout, you can add a customer portal link to allow customers to manage their own subscriptions.

 

Step 1: Configure Stripe Customer Portal

 

First, set up your customer portal configuration in the Stripe Dashboard:

  • Go to Settings → Customer Portal in your Stripe Dashboard
  • Configure the branding, features, and available actions (ensure "Cancel subscriptions" is enabled)
  • Save your configuration

 

Step 2: Create a customer portal session

 

Node.js:

async function createPortalSession(customerId, returnUrl) {
  try {
    const session = await stripe.billingPortal.sessions.create({
      customer: customerId,
      return\_url: returnUrl,
    });
    return session.url;
  } catch (error) {
    console.error('Error creating portal session:', error);
    throw error;
  }
}

// Usage
const portalUrl = await createPortalSession('cus\_12345', 'https://yourwebsite.com/account');

Python:

def create_portal_session(customer_id, return_url):
    try:
        session = stripe.billing\_portal.Session.create(
            customer=customer\_id,
            return_url=return_url,
        )
        return session.url
    except Exception as e:
        print(f"Error creating portal session: {str(e)}")
        raise e

# Usage
portal_url = create_portal_session('cus_12345', 'https://yourwebsite.com/account')

PHP:

function createPortalSession($customerId, $returnUrl) {
    try {
        $session = \Stripe\BillingPortal\Session::create([
            'customer' => $customerId,
            'return\_url' => $returnUrl,
        ]);
        return $session->url;
    } catch (\Exception $e) {
        echo "Error creating portal session: " . $e->getMessage();
        throw $e;
    }
}

// Usage
$portalUrl = createPortalSession('cus\_12345', 'https://yourwebsite.com/account');

Ruby:

def create_portal_session(customer_id, return_url)
  begin
    session = Stripe::BillingPortal::Session.create({
      customer: customer\_id,
      return_url: return_url,
    })
    return session.url
  rescue Stripe::StripeError => e
    puts "Error creating portal session: #{e.message}"
    raise e
  end
end

# Usage
portal_url = create_portal_session('cus_12345', 'https://yourwebsite.com/account')

 

Step 3: Redirect the customer to the portal URL

 

You can redirect your customer to the portal URL where they can manage and cancel their subscriptions.

HTML/JavaScript example:

<!-- Add this button to your account page -->
<button id="manage-subscription">Manage Subscription</button>

<script>
  document.getElementById('manage-subscription').addEventListener('click', async () => {
    // Make an AJAX call to your backend to create a portal session
    const response = await fetch('/create-portal-session', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify({ 
        customerId: 'cus\_12345' // You should get this from your authenticated user data
      }),
    });
    
    const { url } = await response.json();
    
    // Redirect to the portal
    window.location.href = url;
  });
</script>

 

Handling Webhook Events for Subscription Cancellations

 

It's important to listen for webhook events to keep your application in sync with subscription changes.

 

Step 1: Set up a webhook endpoint

 

Node.js (Express):

const express = require('express');
const app = express();
const stripe = require('stripe')('sk_test_your_secret_key');

// Use JSON parser for webhook requests
app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  const endpointSecret = 'whsec_your_webhook_signing_secret';
  
  let event;
  
  try {
    event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }
  
  // Handle the event
  switch (event.type) {
    case 'customer.subscription.deleted':
      const subscription = event.data.object;
      console.log(`Subscription ${subscription.id} was canceled.`);
      
      // Perform any necessary actions in your database or application
      // e.g., update user's access, send notification, etc.
      
      break;
      
    case 'customer.subscription.updated':
      const updatedSubscription = event.data.object;
      if (updatedSubscription.cancel_at_period\_end) {
        console.log(`Subscription ${updatedSubscription.id} was set to cancel at period end.`);
        // Handle accordingly
      }
      break;
      
    // ... handle other event types
    
    default:
      console.log(`Unhandled event type ${event.type}`);
  }
  
  // Return a 200 response to acknowledge receipt of the event
  res.status(200).json({received: true});
});

app.listen(3000, () => console.log('Running on port 3000'));

 

Step 2: Register your webhook in the Stripe Dashboard

 

  • Go to Developers → Webhooks in your Stripe Dashboard
  • Click "Add endpoint"
  • Enter your endpoint URL (e.g., https://yourwebsite.com/webhook)
  • Select events to listen for: at minimum, select "customer.subscription.deleted" and "customer.subscription.updated"
  • Click "Add endpoint" to save

 

Troubleshooting Common Issues

 

Issue 1: Cannot cancel subscription - Permission denied

 

  • Check if you're using the correct API key (test vs. live)
  • Verify that the API key has the necessary permissions
  • Ensure you're the owner of the subscription or have admin access

 

Issue 2: Webhook events not being received

 

  • Verify your webhook endpoint is publicly accessible
  • Check if the endpoint secret is correct
  • Look at the webhook logs in the Stripe Dashboard to see if there are delivery failures

 

Issue 3: Subscription shows as active after cancellation

 

  • Check if you've set cancel_at_period\_end to true instead of immediately canceling
  • Verify the subscription status by retrieving it again
  • Look for any errors in your cancellation request

 

Conclusion

 

Canceling subscriptions in Stripe can be done through multiple methods, each appropriate for different scenarios. By following this guide, you should be able to implement subscription cancellation functionality in your application, whether through the Stripe Dashboard for manual operations, programmatically via the API for backend automation, or through the Customer Portal for self-service management.

Remember to always handle webhook events to keep your application's state synchronized with Stripe's subscription data.

Want to explore opportunities to work with us?

Connect with our team to unlock the full potential of no-code solutions with a no-commitment consultation!

Book a Free Consultation

Client trust and success are our top priorities

When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.

Rapid Dev was an exceptional project management organization and the best development collaborators I've had the pleasure of working with. They do complex work on extremely fast timelines and effectively manage the testing and pre-launch process to deliver the best possible product. I'm extremely impressed with their execution ability.

CPO, Praction - Arkady Sokolov

May 2, 2023

Working with Matt was comparable to having another co-founder on the team, but without the commitment or cost. He has a strategic mindset and willing to change the scope of the project in real time based on the needs of the client. A true strategic thought partner!

Co-Founder, Arc - Donald Muir

Dec 27, 2022

Rapid Dev are 10/10, excellent communicators - the best I've ever encountered in the tech dev space. They always go the extra mile, they genuinely care, they respond quickly, they're flexible, adaptable and their enthusiasm is amazing.

Co-CEO, Grantify - Mat Westergreen-Thorne

Oct 15, 2022

Rapid Dev is an excellent developer for no-code and low-code solutions.
We’ve had great success since launching the platform in November 2023. In a few months, we’ve gained over 1,000 new active users. We’ve also secured several dozen bookings on the platform and seen about 70% new user month-over-month growth since the launch.

Co-Founder, Church Real Estate Marketplace - Emmanuel Brown

May 1, 2024 

Matt’s dedication to executing our vision and his commitment to the project deadline were impressive. 
This was such a specific project, and Matt really delivered. We worked with a really fast turnaround, and he always delivered. The site was a perfect prop for us!

Production Manager, Media Production Company - Samantha Fekete

Sep 23, 2022