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.
// 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'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.
// 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});
});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.
// 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}`);
});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.
// 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 }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.
// 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'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.
// 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
});
}Common Pitfalls
- Assuming all
card_declinedfailures are permanent—many are temporary (insufficient funds that clear the next day, expired cards that were recently renewed, etc.). Always retry. - Ignoring the
decline_codefield—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.