6 min read

How to Track Refund Rate in Stripe

A high refund rate signals trouble—whether that's payment processing errors, product issues, or fraud. Stripe makes it straightforward to see how many charges end up refunded. Here's how to pull that data and track it over time.

View refunds in the Stripe Dashboard

The fastest way to spot refund patterns is Stripe's built-in dashboard. You can filter by date range and payment method to see where refunds cluster.

Navigate to the Refunds view

Log into your Stripe account and go to Payments > Refunds. You'll see a table listing all refunds, sorted by date. Filter by Status, Payment method, Application, or Date range to narrow results. This view is read-only and updated in real time.

Export refund data for analysis

Click the More button (⋯) above the refund table and select Export to CSV. This exports the full list with timestamps, amounts, and reasons. Use this for importing into analytics tools or calculating metrics in a spreadsheet.

javascript
// After exporting, calculate refund rate in your analytics tool:
const refundRate = (totalRefunds / totalCharges) * 100;
console.log(`Refund rate: ${refundRate.toFixed(2)}%`);
// Example: 45 refunds out of 1000 charges = 4.5% refund rate
Basic refund rate formula
Tip: Use the date filter to compare refund rates week-over-week. A sudden spike often points to a specific issue—a failed payment gateway, product launch, or marketing campaign that attracted different customer behavior.

Pull refund data programmatically via the Stripe API

For continuous monitoring or automated reporting, query the Stripe API directly. You can fetch refunds with filters and calculate rates in real time.

Fetch all refunds using the Stripe SDK

Use the Stripe Node SDK to list all refunds within a date range. The refunds.list() method returns paginated results, so use limit and starting_after to handle large datasets. Always set a specific date range to keep response times reasonable.

javascript
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

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

console.log(`Found ${refunds.data.length} refunds in the last 30 days`);
refunds.data.forEach(r => {
  console.log(`Refund: $${(r.amount / 100).toFixed(2)} - ${r.reason}`);
});
Query refunds from the last 30 days

Count charged vs. refunded transactions

Fetch all charges in the same period, then calculate what percentage were refunded. Stripe's Charge object includes refunded: true or refunded: false, so you can count directly. Note that a charge with a partial refund still counts as refunded.

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

const refundedCharges = charges.data.filter(c => c.refunded).length;
const totalCharges = charges.data.length;
const refundRate = (refundedCharges / totalCharges) * 100;

console.log(`Total charges: ${totalCharges}`);
console.log(`Refunded: ${refundedCharges}`);
console.log(`Refund rate: ${refundRate.toFixed(2)}%`);

Store results for trend tracking

Save refund counts and rates to your database or analytics backend on a schedule (e.g., daily cron job). This lets you spot trends over weeks or months, not just point-in-time snapshots. Store the raw numbers and calculated rate.

javascript
const metric = {
  date: new Date().toISOString(),
  refundRate: refundRate,
  totalRefunds: refundedCharges,
  totalCharges: totalCharges,
  refundedAmount: charges.data
    .filter(c => c.refunded)
    .reduce((sum, c) => sum + c.amount, 0) / 100
};

await database.refundMetrics.insert(metric);
console.log(`Stored metric: ${metric.refundRate.toFixed(2)}% refund rate`);
Watch out: If you're running this on a large customer base, the Stripe API has rate limits (100 requests per second). For high-volume businesses, paginate carefully using starting_after, or use the export CSV approach instead.

Set up automated refund monitoring

Rather than manually checking refunds, set up webhooks to track refunds in real time and alert you to unusual activity.

Enable refund webhooks

Go to Developers > Webhooks in your Stripe dashboard. Click Add endpoint and subscribe to charge.refunded events. This fires whenever a refund is created, so you can log it immediately to your monitoring system.

javascript
const express = require('express');
const app = express();
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

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

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

  if (event.type === 'charge.refunded') {
    const refund = event.data.object;
    console.log(`Refund: $${(refund.amount / 100).toFixed(2)} on charge ${refund.id}`);
    // Log to your analytics backend
  }

  res.json({ received: true });
});

Alert on spike in refunds

In your webhook handler, compare the refund count to a rolling average. If refunds exceed 10% above normal, send a Slack or email alert. This catches sudden issues (failed payment flow, fraud) before they spiral.

javascript
const dailyRefundCount = await database.getDailyRefundCount();
const avgRefundRate = (dailyRefundCount.sum / dailyRefundCount.days);
const threshold = avgRefundRate * 1.1; // Alert if 10% above average

if (dailyRefundCount.today > threshold) {
  await slack.send({
    text: `⚠️ Refund rate is high today: ${dailyRefundCount.today} refunds (avg: ${avgRefundRate.toFixed(2)})`
  });
}
Tip: Stripe webhooks can be delayed or retried. Always reconcile webhook data against a scheduled API pull at least once per day to catch missing events or duplicates.

Common Pitfalls

  • Not accounting for partial refunds: A charge with multiple refunds is still counted as one refunded charge in Stripe's data. If you're tracking revenue impact, sum the refund amounts instead of counting refunds.
  • Ignoring the reason field: Refunds due to fraudulent or duplicate are fundamentally different from customer_request. Slice your analysis by reason to see what's actually driving your refund rate.
  • Not filtering by payment status: Some charges fail before settlement. Include only settled or successful charges when calculating refund rate, or your denominator will be inflated.
  • Missing timezone issues: Stripe uses UTC for timestamps. If you're reporting by day, be careful about off-by-one errors when converting to your local timezone.

Wrapping Up

Tracking refund rate in Stripe is essential—it's the fastest signal that something's wrong with payments, your product, or fulfillment. Pull data from the dashboard for quick checks, use the API for programmatic tracking, and set up webhooks to catch spikes early. 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