Skip to content

Subscription Lifecycle Management: Upgrades, Dunning, and Fraud Detection

A practical guide to subscription state machines, proration strategies, dunning management, and fraud detection patterns with Stripe webhooks and AWS EventBridge.

Subscription billing has a short happy path and a long tail of edge cases that silently cost revenue. A naive proration step double-charges upgrades; a default retry policy burns through cards during dunning; missing fraud signals land as chargebacks. Subscriptions are not single transactions; they are state machines, and every billing event is a transition that can fire, fail, or repeat.

This post is a practical guide for backend engineers building subscription systems on Stripe with AWS EventBridge. It covers the lifecycle state machine, proration strategies for mid-cycle changes, dunning patterns that recover revenue without training customers to ignore emails, and the fraud signals that matter before a chargeback.

The Subscription State Machine

Every subscription system is a state machine. Understanding the states and transitions is the foundation for building reliable billing logic.

Each transition maps to a specific Stripe webhook event:

TransitionStripe EventAction
Createdcustomer.subscription.createdProvision access, send welcome
Trial endingcustomer.subscription.trial_will_endPrompt for payment method
Payment successinvoice.payment_succeededConfirm active status
Payment failureinvoice.payment_failedStart dunning workflow
Plan changecustomer.subscription.updatedAdjust entitlements
Cancellationcustomer.subscription.deletedRevoke access at period end

The trial_will_end event fires 3 days before expiration. This is the best window for conversion -- users who add a payment method within this window convert at significantly higher rates than those prompted on the final day.

Upgrade and Downgrade: Getting Proration Right

Proration is where most subscription systems accumulate subtle bugs. The core question: when a customer changes plans mid-cycle, how do you handle the billing difference?

Three Proration Strategies

Immediate proration (Stripe's default): The customer gets credited for unused time on the old plan and charged for remaining time on the new plan. A customer upgrading from 10/monthto10/month to 20/month halfway through the billing period pays an additional 5:5: -5 credit for unused time plus $10 for the remaining half on the new plan.

Next billing cycle: Changes take effect at renewal. Set proration_behavior: 'none' in the Stripe API. This is simpler for customers to understand and generates fewer support tickets. The tradeoff is delayed revenue recognition.

Always invoice: Creates an immediate invoice for the proration amount. Set proration_behavior: 'always_invoice'. This works well for enterprise plans where billing accuracy matters more than user friction.

Choosing a Strategy

AudienceStrategyReason
B2C / Self-serveNext billing cycleSimpler UX, fewer support tickets
B2B / EnterpriseImmediate prorationAccurate billing, audit trail
Usage-basedAlways invoicePrevents revenue leakage

Tip: For B2C products, consider showing the proration amount before the user confirms the change. "Your new plan costs 20/month.Youllbecharged20/month. You'll be charged 5 today for the remainder of this billing period" eliminates surprise charges and reduces chargebacks.

Handling Downgrades

Downgrades are trickier than upgrades. The customer expects a credit or reduced next invoice, but the implementation depends on your refund philosophy:

  • Credit toward next invoice: Apply the unused balance as a credit. No money moves, and the customer sees a reduced charge at renewal
  • Immediate refund: Return the difference to the card. Higher customer satisfaction but increases payment processing costs
  • Account credit: Issue platform credits that can be used for add-ons or future billing. Keeps the money in your ecosystem

Dunning: Recovering Failed Payments

Dunning -- the process of recovering failed subscription payments -- is one of the highest-leverage areas in subscription management. Involuntary churn from payment failures accounts for 20-40% of total churn in most SaaS businesses.

Smart Retry Logic

Not all payment failures are equal. The retry strategy should adapt based on the decline reason:

Decline CodeMeaningRetry Strategy
insufficient_fundsCard has no balanceWait 3-5 days (align with payday cycles)
card_expiredCard past expirationDo not retry -- request card update
do_not_honorIssuer refusedRetry once after 24 hours, then request alternative payment
network_errorTemporary connectivityRetry within 2-4 hours
fraudulentSuspected fraudDo not retry -- flag for review

Stripe Smart Retries use machine learning to optimize retry timing based on the issuing bank's behavior patterns. This approach can reduce soft declines by over 20% compared to static retry schedules.

A practical finding: roughly 50% of all payment recovery comes from retries alone, with about 21% of failed payments recovered through automatic retries before the first dunning email is even sent.

Dunning Communication Cadence

Key implementation details for dunning communications:

  • One-click payment update links: Every email should include a direct link to update payment details without requiring login. Over 70% of card updates happen on mobile devices, so the update flow must be mobile-optimized
  • Personalized messaging: Personalized dunning emails are significantly more effective than generic templates. Use the customer's name, mention their plan, and be empathetic
  • Card updater services: Visa Account Updater (VAU) and Mastercard Automatic Billing Updater (ABU) can automatically update expired card details. This alone prevents 30-50% of hard declines from expired cards

Grace Period Strategies

The grace period determines how long a customer retains access after a payment failure. Different tiers warrant different treatment:

Plan TierGrace PeriodAccess During Grace
Basic7 daysFull access with banner
Pro14 daysFull access with banner
Enterprise30 daysFull access, account manager notified

Store the grace period end date in the subscription metadata. Check entitlement status on each API request and show in-app banners that communicate the payment issue without blocking the user's workflow.

Fraud Detection Patterns

Subscription systems are attractive targets for fraud because they allow repeated charges against stolen payment methods over extended periods.

Card Testing Attacks

Card testing is when fraudsters validate stolen card numbers by making small subscription charges. The pattern is recognizable: multiple subscription attempts from the same IP or device fingerprint in a short time window.

Defense measures:

  • Rate limit subscription creation: Maximum 3 attempts per IP per 15-minute window
  • Require CAPTCHA after 2 failed attempts
  • Monitor card BIN patterns -- multiple cards from the same BIN range suggest a compromised batch
  • Block known disposable email domains for trial signups

Velocity Checks

Velocity checks monitor transaction frequency to catch anomalous behavior:

typescript
interface VelocityRule {  dimension: 'ip' | 'device_fingerprint' | 'email_domain' | 'card_bin';  window: number; // seconds  maxAttempts: number;  action: 'block' | 'flag' | 'challenge';}
const velocityRules: VelocityRule[] = [  { dimension: 'ip', window: 900, maxAttempts: 5, action: 'block' },  { dimension: 'device_fingerprint', window: 3600, maxAttempts: 3, action: 'block' },  { dimension: 'email_domain', window: 86400, maxAttempts: 10, action: 'flag' },  { dimension: 'card_bin', window: 3600, maxAttempts: 5, action: 'challenge' },];

Trial Abuse Detection

Trial abuse -- where the same person creates multiple accounts to get repeated free trials -- is a persistent problem. Common detection signals:

  • Same device fingerprint across multiple accounts
  • Similar email patterns ([email protected], [email protected])
  • Same payment method linked to multiple accounts
  • Identical browser/device configuration from different "users"

Stripe Radar provides risk scoring on each payment attempt. Setting custom rules like blocking transactions with a risk score above 75 on subscription creation catches most automated fraud while maintaining a low false-positive rate.

Chargeback Monitoring

Keep chargeback ratios below critical thresholds:

  • Visa VAMP: 1.5% excessive ratio (dropping to 0.9% in 2026)
  • Mastercard: 1.0% of transactions

Exceeding these thresholds can result in enrollment in monitoring programs, per-dispute fees, or loss of card processing ability.

Churn Recovery

Involuntary Churn

Payment failures cause 20-40% of total churn. The dunning strategies above are the primary defense. Additional measures:

  • Payment method diversification: Offer PayPal, SEPA Direct Debit, or bank transfer as fallback methods
  • Automated card updater integration: Pre-emptively update expiring cards before they fail
  • Pre-dunning notifications: Alert customers 7 days before card expiration

Voluntary Churn

When a customer initiates cancellation, the cancellation flow itself is a retention opportunity:

  1. Ask why: Present 3-5 cancellation reasons (too expensive, not using it, missing features, switching to competitor, other)
  2. Offer alternatives: Based on the reason, offer pause, downgrade, or discount
  3. Retention offer: A 10-30% discount for 3 months recovers a meaningful portion of cancellation attempts
  4. Confirm and follow up: If they proceed, send a confirmation and schedule win-back emails at day 7, day 30, and day 90

Tip: Track the "save rate" -- the percentage of users who start the cancellation flow but don't complete it. This metric directly measures the effectiveness of your retention offers.

Event-Driven Architecture: Stripe + EventBridge

The code example below shows a webhook handler that receives Stripe events and routes them through AWS EventBridge for downstream processing. This pattern decouples the webhook handler from business logic, making the system easier to extend and debug.

typescript
import Stripe from 'stripe';import { EventBridgeClient, PutEventsCommand } from '@aws-sdk/client-eventbridge';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!);const eventBridge = new EventBridgeClient({});
const EVENT_BUS_NAME = process.env.EVENT_BUS_NAME || 'subscription-events';
interface WebhookResult {  statusCode: number;  body: string;}
export async function handler(event: {  body: string;  headers: Record<string, string>;}): Promise<WebhookResult> {  const signature = event.headers['stripe-signature'];
  let stripeEvent: Stripe.Event;  try {    stripeEvent = stripe.webhooks.constructEvent(      event.body,      signature,      process.env.STRIPE_WEBHOOK_SECRET!    );  } catch (err) {    console.error('Webhook signature verification failed:', err);    return { statusCode: 400, body: 'Invalid signature' };  }
  // Idempotency check - skip if we've already processed this event  const alreadyProcessed = await checkIdempotency(stripeEvent.id);  if (alreadyProcessed) {    return { statusCode: 200, body: 'Already processed' };  }
  // Map Stripe event to EventBridge detail-type  const detailType = mapEventType(stripeEvent.type);
  await eventBridge.send(    new PutEventsCommand({      Entries: [        {          Source: 'subscription.stripe-webhook',          DetailType: detailType,          Detail: JSON.stringify({            stripeEventId: stripeEvent.id,            type: stripeEvent.type,            subscription: extractSubscriptionData(stripeEvent),            timestamp: new Date().toISOString(),          }),          EventBusName: EVENT_BUS_NAME,        },      ],    })  );
  // Mark as processed for idempotency  await markProcessed(stripeEvent.id);
  return { statusCode: 200, body: 'OK' };}
function mapEventType(stripeType: string): string {  const mapping: Record<string, string> = {    'customer.subscription.created': 'SubscriptionCreated',    'customer.subscription.updated': 'SubscriptionUpdated',    'customer.subscription.deleted': 'SubscriptionCanceled',    'invoice.payment_succeeded': 'PaymentSucceeded',    'invoice.payment_failed': 'PaymentFailed',    'customer.subscription.trial_will_end': 'TrialEnding',  };  return mapping[stripeType] || 'UnknownEvent';}
function extractSubscriptionData(event: Stripe.Event) {  const obj = event.data.object as Stripe.Subscription | Stripe.Invoice;  if ('subscription' in obj) {    // Invoice event    return {      subscriptionId: obj.subscription,      customerId: obj.customer,      amountDue: (obj as Stripe.Invoice).amount_due,      status: (obj as Stripe.Invoice).status,    };  }  // Subscription event  const sub = obj as Stripe.Subscription;  return {    subscriptionId: sub.id,    customerId: sub.customer,    status: sub.status,    currentPeriodEnd: sub.current_period_end,    cancelAtPeriodEnd: sub.cancel_at_period_end,  };}

This handler does three things: verifies the webhook signature, checks for idempotent processing, and publishes a normalized event to EventBridge. Downstream consumers -- dunning workflows, entitlement updates, analytics -- subscribe to specific event types without coupling to the webhook handler.

For a deeper look at how different payment providers handle subscription billing, see the payment providers comparison. For how subscription state changes propagate across mobile and web platforms, see the entitlement sync architecture.

Key Takeaways

  • Model subscriptions as a state machine. Every state transition should map to a specific webhook event and trigger a defined business action
  • Choose proration strategy based on your audience. B2C benefits from next-cycle changes; B2B needs immediate accuracy
  • Invest heavily in dunning. Smart retries and personalized communication can recover 70-85% of failed payments
  • Layer fraud detection. Velocity checks, device fingerprinting, and Stripe Radar together catch most abuse patterns without blocking legitimate customers
  • Separate webhook handling from business logic. EventBridge decouples the Stripe integration from downstream processing, making the system easier to maintain and extend

References

  1. Using webhooks with subscriptions - Stripe's official guide to subscription webhook events and recommended handling patterns.

  2. How subscriptions work - Stripe documentation covering subscription lifecycle, statuses, and billing behavior.

  3. Prorations - Stripe's explanation of how proration calculations work for plan changes.

  4. Modify subscriptions - Stripe API guide for upgrading, downgrading, and changing subscription plans.

  5. Beyond webhooks: Event-driven payment architectures with Amazon EventBridge - AWS blog on building event-driven payment processing with EventBridge.

  6. Guidance for Building Payment Systems Using Event-Driven Architecture on AWS - AWS Solutions Library reference architecture for payment systems.

  7. Streamlining Financial Operations: Leveraging Stripe event destinations with Amazon EventBridge - AWS blog on native Stripe-EventBridge integration.

  8. What Is Dunning Management? Why It Matters & Best Practices - Maxio's comprehensive guide to dunning management strategies and recovery rates.

  9. Subscription Dunning: Recover 80% of Failed Payments - ProsperStack's analysis of dunning effectiveness and best practices.

  10. How card testing and digital skimming are evolving - Mastercard's overview of card testing attack patterns and defense mechanisms.

  11. Velocity Check: Fraud Prevention Technique - SEON's guide to implementing velocity checks for payment fraud prevention.

  12. Stripe Radar - Stripe's machine learning fraud detection platform documentation.

  13. What are velocity checks & how do they prevent payment fraud? - Checkout.com's technical explanation of velocity-based fraud prevention.

Related Posts