customer.subscription.deleted
event when a free trial ends and payment fails.trial_end
, ended_at
, cancel_at_period_end
, and request
.payment_failed
events.When a customer subscribes to a plan that includes a free trial, Stripe manages the trial period by placing the subscription in a "trialing" state. During this phase, the customer can access the service without being charged. However, once the trial period concludes, Stripe attempts to transition the subscription to an "active" state by charging the customer's default payment method.
If the payment succeeds, the subscription moves to "active," and regular billing commences according to the subscription's billing cycle. Conversely, if the payment fails, Stripe handles the transition based on the subscription's configuration, which can include automatic cancellation.
customer.subscription.deleted
EventStripe emits the customer.subscription.deleted
event in several scenarios, including when:
cancel_at_period_end
is true
).The customer.subscription.deleted
event payload contains valuable information to determine the reason for the cancellation. Key attributes to examine include:
Attribute | Description |
---|---|
status |
Indicates the subscription status, which will be canceled . |
trial_end |
Unix timestamp representing when the trial period ended. |
ended_at |
Unix timestamp indicating when the subscription was actually canceled. |
cancel_at_period_end |
Boolean flag showing if the subscription was set to cancel at the end of the billing period. |
request |
Contains information about the cancellation request. If null , the cancellation was automatic. |
default_payment_method |
Details of the payment method on file. null indicates no valid payment method was provided. |
To determine if a subscription was canceled automatically at the end of a free trial due to a failed payment, focus on the following attributes within the customer.subscription.deleted
event:
trial_end
and ended_at
Timestamps:
Compare these two timestamps. If they are identical or nearly identical, it signifies that the subscription was canceled immediately after the trial ended, likely due to a payment failure.
cancel_at_period_end
Flag:
If this flag is true
, the subscription was configured to cancel automatically at the end of the trial or billing period.
request
Attribute:
An empty or null
request
attribute indicates an automatic cancellation initiated by Stripe, as opposed to a manual cancellation by the user or merchant.
default_payment_method
:
A null
value here suggests that no valid payment method was provided, leading to the automatic cancellation after trial expiration.
Consider a subscription where a customer is on a free 30-day trial. They do not provide a valid credit card, or the payment method fails at the end of the trial. Stripe will attempt to transition the subscription to "active" by charging the card. Upon failure, if configured to cancel, Stripe sends a customer.subscription.deleted
event with the following characteristics:
{
"id": "evt_123...",
"object": "event",
"type": "customer.subscription.deleted",
"data": {
"object": {
"id": "sub_123...",
"customer": "cus_456...",
"status": "canceled",
"trial_end": 1679875200,
"canceled_at": 1679875200,
"cancel_at_period_end": true,
"default_payment_method": null,
// Other attributes...
}
}
}
In this example:
trial_end
equals canceled_at
, indicating the cancellation occurred right after the trial ended.cancel_at_period_end
is true
, confirming the subscription was set to cancel at the end of the trial.default_payment_method
is null
, showing the absence of a valid payment method.To effectively handle subscription cancellations due to payment failures, set up a webhook endpoint that listens for the customer.subscription.deleted
event. This ensures your application is notified in real-time when such events occur.
The following Python example demonstrates how to handle the customer.subscription.deleted
event:
import json
from flask import Flask, request
app = Flask(__name__)
@app.route('/webhook', methods=['POST'])
def webhook():
event = json.loads(request.data)
if event['type'] == 'customer.subscription.deleted':
subscription = event['data']['object']
trial_end = subscription.get('trial_end')
ended_at = subscription.get('canceled_at')
cancel_at_period_end = subscription.get('cancel_at_period_end', False)
payment_method = subscription.get('default_payment_method')
request_info = subscription.get('request')
# Check if cancellation is automatic due to trial end and payment failure
if trial_end and ended_at and trial_end == ended_at:
if not request_info and not payment_method:
# Automatic cancellation due to payment failure
handle_subscription_cancellation(subscription)
# Additional handling for other cancellation scenarios
elif cancel_at_period_end:
handle_scheduled_cancellation(subscription)
else:
handle_manual_cancellation(subscription)
return '', 200
def handle_subscription_cancellation(subscription):
customer_id = subscription['customer']
subscription_id = subscription['id']
# Implement logic to revoke access, notify user, etc.
print(f"Subscription {subscription_id} for customer {customer_id} canceled due to payment failure.")
def handle_scheduled_cancellation(subscription):
customer_id = subscription['customer']
subscription_id = subscription['id']
# Implement logic for scheduled cancellations
print(f"Subscription {subscription_id} for customer {customer_id} scheduled to cancel at period end.")
def handle_manual_cancellation(subscription):
customer_id = subscription['customer']
subscription_id = subscription['id']
# Implement logic for manual cancellations
print(f"Subscription {subscription_id} for customer {customer_id} was manually canceled.")
if __name__ == '__main__':
app.run(port=4242)
1. Webhook Endpoint Setup: The Flask app defines a route /webhook
to receive POST requests from Stripe's webhook service.
2. Event Verification: The code checks if the event type is customer.subscription.deleted
, ensuring that only relevant events are processed.
3. Attribute Extraction and Comparison: It extracts key attributes such as trial_end
, canceled_at
, cancel_at_period_end
, default_payment_method
, and request
. By comparing trial_end
and canceled_at
, and checking if request
is null
, it determines if the cancellation was due to a payment failure at the end of the trial.
4. Handling the Cancellation: Depending on the scenario, appropriate actions are taken, such as revoking access, sending notifications, or updating internal records.
payment_failed
EventsWhile payment_failed
events can indicate issues with payment methods, they are not definitive indicators of subscription cancellations, as they might represent temporary problems (e.g., insufficient funds, transient network issues). Relying solely on these events can lead to false positives and unintended access revocations.
customer.subscription.deleted
for Definitive Cancellation DetectionBy focusing on the customer.subscription.deleted
event and validating key attributes, you ensure that only confirmed cancellations due to payment failures at trial end are acted upon. This approach minimizes the risk of prematurely canceling active subscriptions.
For enhanced user experience, consider implementing a grace period allowing users to update their payment information before finalizing the cancellation. Stripe offers subscription pausing features that can be integrated into your webhook handling logic.
When comparing timestamps like trial_end
and canceled_at
, account for potential minor differences due to processing times. Using a small buffer or considering them "very close" within a reasonable timeframe can improve accuracy.
Upon detecting an automatic cancellation, proactively notify the user via email or in-app notifications. Inform them about the cancellation, the reason behind it (e.g., payment failure), and provide steps to reactivate their subscription by updating payment information.
Encourage users to add or update their payment methods to seamlessly continue their subscriptions. Providing clear and accessible options for updating payment details can reduce churn and improve user retention.
Maintain detailed logs of webhook events and handling outcomes. Implement monitoring tools to alert your team of any anomalies or failures in processing critical events, ensuring timely interventions when issues arise.
Thoroughly test your webhook handling logic in Stripe's test environment. Simulate various scenarios, including successful payments, failed payments, manual cancellations, and automatic cancellations, to ensure your system behaves as expected under different conditions.
Managing subscription cancellations due to payment failures at the end of a free trial is a critical aspect of maintaining a healthy SaaS business. By leveraging Stripe's customer.subscription.deleted
event and carefully inspecting key attributes within the event payload, you can accurately identify and handle these cancellations without falling prey to false positives from transient payment failures.
Implementing a robust webhook handling system not only ensures accurate subscription management but also enhances user experience by providing timely notifications and opportunities to rectify payment issues. Adhering to best practices in webhook handling, logging, and user communication will contribute significantly to minimizing churn and fostering long-term customer relationships.