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.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
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
Step 7: Choose cancellation options
You'll have two main options:
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:
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
Troubleshooting Common Issues
Issue 1: Cannot cancel subscription - Permission denied
Issue 2: Webhook events not being received
Issue 3: Subscription shows as active after cancellation
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.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.