5 min read

What Is Refund Rate in Stripe

Your refund rate is the percentage of completed transactions that get refunded. In Stripe, this metric is critical—it tells you whether customers are satisfied with their purchases or if something's broken. A spike in refunds often signals a problem worth investigating immediately.

Understanding Refund Rate in Stripe

Refund rate is straightforward: (refunds / successful charges) × 100. But knowing where to get this data in Stripe is half the battle.

Know where refunds live in Stripe

Stripe tracks every charge and refund separately. A Charge is a successful payment, and a Refund is money returned to the customer. You can see both in the Transactions dashboard, but the API is where you'll get the full picture for analysis. Every refund is linked to its original charge via the charge field.

javascript
const stripe = require('stripe')('sk_live_YOUR_KEY');

// Fetch refunds created in the last 30 days
const refunds = await stripe.refunds.list({
  created: {
    gte: Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60,
    lte: Math.floor(Date.now() / 1000)
  },
  limit: 100
});

console.log(`Found ${refunds.data.length} refunds`);
refunds.data.forEach(refund => {
  console.log(`Refund ${refund.id}: $${refund.amount / 100} on charge ${refund.charge}`);
});
Retrieve all refunds from the last 30 days

Understand refund status in Stripe

Not all refunds are the same. A refund can be succeeded (money returned), pending (in transit), or failed (rejected by the bank). Only succeeded refunds should count toward your refund rate. Check the status field on each refund object.

javascript
// Filter for only succeeded refunds
const succeededRefunds = refunds.data.filter(r => r.status === 'succeeded');
const failedRefunds = refunds.data.filter(r => r.status === 'failed');

console.log(`Succeeded: ${succeededRefunds.length}`);
console.log(`Failed: ${failedRefunds.length}`);

// Most of the time you care about succeeded refunds
const totalRefundedAmount = succeededRefunds.reduce((sum, r) => sum + r.amount, 0);
console.log(`Total refunded: $${totalRefundedAmount / 100}`);
Count only succeeded refunds to get accurate metrics
Watch out: Stripe's refunds.list() returns paginated results. If you have thousands of refunds, you'll need to handle pagination or set higher limit values (max 100 per request).

Calculate Your Refund Rate

The math is simple, but you need to be careful about what time period you're measuring and which charges count.

Fetch successful charges for the same period

To get your refund rate, divide refunds by the charges that could have been refunded. Use stripe.charges.list() with the same date range you used for refunds. Filter for status: 'succeeded' to only count completed payments.

javascript
// Fetch charges from the same 30-day period
const charges = await stripe.charges.list({
  created: {
    gte: Math.floor(Date.now() / 1000) - 30 * 24 * 60 * 60,
    lte: Math.floor(Date.now() / 1000)
  },
  limit: 100
});

const succeededCharges = charges.data.filter(c => c.status === 'succeeded');
console.log(`Total successful charges: ${succeededCharges.length}`);
Count all successful charges in your period

Calculate the percentage

Divide succeeded refunds by succeeded charges and multiply by 100. A refund rate below 1% is healthy for most products. Above 5% typically means something's wrong—either your product quality, your onboarding, or your refund policy is too lenient.

javascript
const refundRate = (succeededRefunds.length / succeededCharges.length) * 100;
console.log(`Refund rate: ${refundRate.toFixed(2)}%`);

// Also useful: refund amount as percentage of revenue
const totalChargedAmount = succeededCharges.reduce((sum, c) => sum + c.amount, 0);
const refundAmountRate = (totalRefundedAmount / totalChargedAmount) * 100;
console.log(`Refund amount rate: ${refundAmountRate.toFixed(2)}%`);
Both transaction count and dollar amount matter
Tip: Some people track refund rate by amount (dollars refunded / dollars charged) instead of by count. Both metrics are useful. High transaction refund rate with low dollar refund rate might mean your cheapest customers are the unhappiest.

Monitor and Investigate Spikes

Once you know your baseline refund rate, set up monitoring to catch problems early.

Set up weekly tracking in your backend

Don't calculate refund rate manually each time. Build a cron job that runs weekly and stores the metric in your database. Compare this week's rate to last week's. A sudden 50% jump means you need to investigate immediately—could be a bug, a bad batch of products, or fraudulent activity.

javascript
// Weekly cron job (e.g., every Monday 9 AM UTC)
const calculateWeeklyRefundRate = async () => {
  const weekAgo = Math.floor(Date.now() / 1000) - 7 * 24 * 60 * 60;
  const now = Math.floor(Date.now() / 1000);

  const refunds = await stripe.refunds.list({
    created: { gte: weekAgo, lte: now },
    limit: 100
  });
  const succeeded = refunds.data.filter(r => r.status === 'succeeded');

  const charges = await stripe.charges.list({
    created: { gte: weekAgo, lte: now },
    limit: 100
  });
  const successfulCharges = charges.data.filter(c => c.status === 'succeeded');

  const rate = (succeeded.length / successfulCharges.length) * 100;
  
  // Store in your database
  await saveMetric('refund_rate', rate, new Date());
  
  console.log(`Week of ${new Date(weekAgo * 1000).toISOString()}: ${rate.toFixed(2)}%`);
};
Track refund rate over time to spot trends

Investigate anomalies in the Stripe dashboard

When your metric spikes, head to the Stripe Dashboard > Transactions and sort by date. Look for patterns: Are refunds concentrated on a single product, country, or payment method? Use Filters to narrow down. If you see a cluster of refunds from a specific date, check your release notes—you probably shipped a bug.

javascript
// If you detect a spike, pull detailed refund data
const detailedRefunds = await stripe.refunds.list({
  created: {
    gte: Math.floor(Date.now() / 1000) - 2 * 24 * 60 * 60,
    lte: Math.floor(Date.now() / 1000)
  },
  limit: 100,
  expand: ['data.charge']
});

detailedRefunds.data.forEach(refund => {
  const charge = refund.charge;
  console.log(`Refund: ${refund.id}`);
  console.log(`  Reason: ${refund.reason}`);
  console.log(`  Metadata: ${JSON.stringify(charge.metadata)}`);
});
Expand charge data to find patterns in refunds
Watch out: Stripe's API returns data in reverse chronological order. If you have thousands of refunds, limit: 100 won't capture all of them in one call. Paginate through results using starting_after to ensure you're not missing any.

Common Pitfalls

  • Not filtering by status: 'succeeded' — pending and failed refunds shouldn't count toward your rate
  • Including test mode refunds in your calculation — use separate API keys or filter by livemode: true
  • Comparing refund rates across different time periods without accounting for seasonality or product launches
  • Forgetting that Stripe API only goes back 3 years in history — if you need older data, log it yourself

Wrapping Up

Refund rate is a leading indicator of product or customer satisfaction issues. Track it weekly, set a threshold that makes sense for your business (usually 1-3%), and investigate any spike immediately. If you want to track this automatically across tools, Product Analyst can help.

Track these metrics automatically

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

Try Product Analyst — Free