Chat
Search
Ithy Logo

Handling Stripe's customer.subscription.deleted Event After Free Trial Ends

Comprehensive Guide to Managing Subscription Cancellations Due to Payment Failures

Stripe payment failure process

Key Takeaways

  • Stripe sends a customer.subscription.deleted event when a free trial ends and payment fails.
  • Key attributes to inspect include trial_end, ended_at, cancel_at_period_end, and request.
  • Implementing robust webhook handling ensures accurate subscription management without false positives from payment_failed events.

Understanding Stripe's Subscription Lifecycle

Free Trial Mechanics

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.

Subscription Transition Post-Trial

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.

The customer.subscription.deleted Event

When is the Event Triggered?

Stripe emits the customer.subscription.deleted event in several scenarios, including when:

  • A subscription is manually canceled by the customer or merchant.
  • An automatic cancellation occurs, such as when a payment fails after a free trial ends.
  • The subscription is set to cancel at the end of the billing period (cancel_at_period_end is true).

Event Payload Structure

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.

Identifying Automatic Cancellations Due to Payment Failures

Key Indicators in the Event Payload

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.

Example Scenario

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.

Implementing Webhook Handling

Setting Up a Webhook Listener

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.

Sample Implementation in Python

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)
  

Explanation of the Code

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.


Best Practices for Handling Subscription Cancellations

Avoid Relying Solely on payment_failed Events

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

Use customer.subscription.deleted for Definitive Cancellation Detection

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

Implement Grace Periods if Necessary

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.

Ensure Accurate Timestamp Comparisons

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.


Extending Functionality Beyond Basic Handling

Notifying Users of Cancellation

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.

Reintegrating Valid Payment Methods

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.

Logging and Monitoring Webhook Events

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.

Testing Webhook Handling

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.


Conclusion

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.


References


Last updated January 25, 2025
Ask Ithy AI
Export Article
Delete Article