6 min read

What Is Failed Payments in Stripe

A failed payment happens when a customer's card is declined—whether due to insufficient funds, expired credentials, or fraud prevention. In Stripe, understanding why payments fail and how to respond is the difference between retaining a customer and losing them to churn. Unlike successful charges you can forget about, failed payments demand immediate action.

What Makes a Payment Fail in Stripe

Stripe distinguishes between payment failures and successful charges. When a payment fails, Stripe records it with a specific failure reason.

Understand Stripe's Payment Failure States

A payment fails when a PaymentIntent or Charge reaches a failed or requires_action state. Common failure reasons include card_declined, expired_card, insufficient_funds, lost_card, stolen_card, and processing_error. Stripe's fraud detection (Radar) can also trigger declines. Each decline comes with a decline code in the API response.

javascript
// Fetch a failed charge to inspect the failure reason
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

const charge = await stripe.charges.retrieve('ch_1A8xvH...');
console.log('Failure reason:', charge.failure_code); // e.g., 'card_declined'
console.log('Failure message:', charge.failure_message); // e.g., 'Your card was declined'
Use the Charges API to check why a specific charge failed

Listen for Payment Failure Events

Set up a webhook endpoint to receive charge.failed or payment_intent.payment_failed events. These fire automatically when Stripe detects a failure, allowing you to respond in real time. Configure webhooks in your Stripe Dashboard > Developers > Webhooks, or use the API.

javascript
// Handle a payment failure webhook
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

app.post('/webhook', express.raw({type: 'application/json'}), async (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;

  try {
    event = stripe.webhooks.constructEvent(req.body, sig, process.env.WEBHOOK_SECRET);
  } catch (e) {
    return res.status(400).send(`Webhook Error: ${e.message}`);
  }

  if (event.type === 'charge.failed') {
    const charge = event.data.object;
    console.log(`Payment failed: ${charge.id}, reason: ${charge.failure_code}`);
    // Send recovery email, trigger retry, etc.
  }

  res.json({received: true});
});
Express server receiving and processing charge.failed events
Watch out: Webhook delays can occur. Don't rely solely on webhooks for critical payment reconciliation—periodically query Stripe's API to confirm payment status.

Tracking and Analyzing Failed Payments

Once you know failures are happening, you need visibility into their patterns: which customers are affected, what decline codes are most common, and how often retries succeed.

Query Failed Charges via the API

Use the charges.list() method with a filter for failed charges. You can filter by date range, customer, or decline code to segment your failures.

javascript
// List all failed charges in the last 7 days
const sevenDaysAgo = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60;
const failedCharges = await stripe.charges.list({
  limit: 100,
  created: { gte: sevenDaysAgo }
});

const failures = failedCharges.data.filter(c => c.status === 'failed');
failures.forEach(charge => {
  console.log(`${charge.customer}: ${charge.failure_code}`);
});
Pull failed charges and segment by decline code

Monitor Failed Payments in the Dashboard

Visit Stripe Dashboard > Payments to see transaction history with decline codes. For higher-level analysis, use Stripe Radar to review decline patterns and risk scores, or export payment data to your analytics tool.

javascript
// Aggregate failure stats by decline code
const failureStats = {};
failures.forEach(charge => {
  const code = charge.failure_code || 'unknown';
  failureStats[code] = (failureStats[code] || 0) + 1;
});

console.table(failureStats);
// Output: { card_declined: 42, expired_card: 18, insufficient_funds: 12 }
Summarize failure codes to identify the most common issues
Tip: Export your failure data to a BI tool like Looker or Metabase for trend analysis. Failed payment rates often correlate with churn—track it like a core metric.

Recovering from Failed Payments

A failed payment isn't permanent. Stripe offers built-in recovery mechanisms: automatic retries, dunning workflows, and custom recovery flows.

Enable Automatic Retry on Subscriptions

For subscriptions, Stripe automatically retries failed payments according to your Billing Settings > Retry Rules. You can configure up to 4 retry attempts over 5 days (default is 2 retries). This is one of the easiest ways to recover revenue—many declines are temporary.

javascript
// Create a subscription with default retry behavior
const subscription = await stripe.subscriptions.create({
  customer: 'cus_1A...',
  items: [{price: 'price_1A...'}],
  default_payment_method: 'pm_1A...'
});

// Stripe will automatically retry the invoice if the first charge fails
console.log('Subscription created, automatic retries enabled');

// Later, check the invoice status
const invoice = await stripe.invoices.retrieve('in_1A...');
console.log('Invoice status:', invoice.status); // 'open', 'draft', 'paid', or 'void'
Subscriptions inherit your account's retry rules automatically

Send Dunning Communications to Customers

Use Stripe's Dunning feature (in Billing Settings > Dunning Emails) to automatically email customers when payments fail, asking them to update their payment method. Or trigger custom emails via invoice.payment_failed webhooks.

javascript
// Respond to a failed invoice with a custom dunning email
if (event.type === 'invoice.payment_failed') {
  const invoice = event.data.object;
  const customer = await stripe.customers.retrieve(invoice.customer);

  // Send custom recovery email
  await sendEmail({
    to: customer.email,
    subject: 'Your payment failed—update your card',
    body: `Hi ${customer.name}, your recent payment didn't go through. Update your card: [link to checkout]`
  });

  // Optionally retry manually
  await stripe.invoices.pay(invoice.id, {
    paid_out_of_band: false
  });
}
Combine webhooks with outbound communication for higher recovery
Tip: A well-tuned dunning strategy can recover 15–40% of failed payments. Pair automatic retries with friendly customer communication for best results.

Common Pitfalls

  • Assuming all card_declined failures are permanent—many are temporary (insufficient funds that clear the next day, expired cards that were recently renewed, etc.). Always retry.
  • Ignoring the decline_code field—it tells you *why* the card was declined, which determines if a retry will work or if you need to ask the customer to take action.
  • Relying on webhooks alone for payment reconciliation—network delays or webhook failures can cause you to miss events. Periodically reconcile against the Stripe API.
  • Not segmenting failures by customer or payment method—some customers have chronic payment issues while others fail once. Tailor recovery based on failure patterns.

Wrapping Up

Failed payments in Stripe are inevitable, but they're recoverable with the right tooling. By listening to failure events, analyzing decline codes, and implementing automatic retries paired with dunning emails, you can recoup 15–40% of revenue that would otherwise disappear to churn. If you want to track failed payments automatically across tools and correlate them with customer lifetime value, Product Analyst can help.

Track these metrics automatically

Product Analyst connects to your stack and surfaces the insights that matter.

Try Product Analyst — Free