A spike in refunds is often your first signal that something's broken—product quality, checkout friction, or customer expectation mismatch. Stripe doesn't have built-in refund rate alerts, but you can build them in minutes using the Events API and webhooks to catch spikes before they become a problem.
Capture refund events with webhooks
Start by setting up a webhook endpoint to receive real-time notifications whenever a refund occurs. This is where you'll log every refund event for later analysis.
Create a webhook endpoint
Build an endpoint that Stripe will POST to whenever a charge.refunded event happens. This endpoint is your connection point—it captures the refund data in real time so you can process it.
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
app.post('/webhooks/stripe', 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) {
return res.status(400).send(`Webhook Error: ${err.message}`);
}
if (event.type === 'charge.refunded') {
const charge = event.data.object;
console.log(`Refund: ${charge.id}, amount: $${charge.refunded / 100}`);
// Log to database or cache for alert calculation
}
res.json({ received: true });
});Register the webhook in Stripe Dashboard
Go to Developers > Webhooks in your Stripe Dashboard. Click Add endpoint, paste your webhook URL, and select charge.refunded as the event to listen for. Stripe will generate a signing secret—save it as STRIPE_WEBHOOK_SECRET in your environment.
Calculate refund rate from Stripe charges
With webhooks running, fetch historical charges and refunds to calculate your current refund rate. This becomes your baseline for comparison.
Pull charges and refunds over a time window
Query all charges from the past 24 hours using the Stripe API. Calculate the total refunded amount and divide by total charged amount to get your refund rate percentage.
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
async function calculateRefundRate(hoursBack = 24) {
const startTime = Math.floor((Date.now() - hoursBack * 3600000) / 1000);
const charges = await stripe.charges.list({
created: { gte: startTime },
limit: 100
});
let totalCharged = 0;
let totalRefunded = 0;
for (const charge of charges.data) {
totalCharged += charge.amount;
if (charge.refunded) {
totalRefunded += charge.refunded;
}
}
const refundRatePercent = totalCharged > 0 ? (totalRefunded / totalCharged) * 100 : 0;
return { refundRatePercent, totalCharged, totalRefunded };
}
const { refundRatePercent } = await calculateRefundRate(24);
console.log(`Refund rate (24h): ${refundRatePercent.toFixed(2)}%`);starting_after parameter to fetch all charges.Trigger alerts when refunds exceed threshold
Define your acceptable refund rate, then automatically notify your team when it's exceeded. This is where you move from monitoring to action.
Check refund rate against your threshold
Set a threshold that makes sense for your business (2% for SaaS, 5% for e-commerce). Compare the calculated rate to this threshold and flag it if exceeded.
async function checkRefundRateAlert(thresholdPercent = 3) {
const { refundRatePercent } = await calculateRefundRate(24);
if (refundRatePercent > thresholdPercent) {
console.warn(
`ALERT: Refund rate ${refundRatePercent.toFixed(2)}% exceeds threshold ${thresholdPercent}%`
);
return { alerted: true, refundRatePercent };
}
return { alerted: false, refundRatePercent };
}
await checkRefundRateAlert(3); // Alert if refund rate > 3%Send notifications to your team
When an alert fires, send a message to Slack, email, or PagerDuty with the current rate and a link to investigate in Stripe. Include the threshold so your team knows why the alert triggered.
const axios = require('axios');
async function alertTeam(refundRate, threshold) {
const message = {
text: `Refund rate alert`,
blocks: [
{
type: 'section',
text: {
type: 'mrkdwn',
text: `⚠️ *Refund rate: ${refundRate.toFixed(2)}%* (threshold: ${threshold}%)\nInvestigate: https://dashboard.stripe.com/transactions`
}
}
]
};
await axios.post(process.env.SLACK_WEBHOOK_URL, message);
console.log('Alert sent to Slack');
}
const { alerted, refundRatePercent } = await checkRefundRateAlert(3);
if (alerted) {
await alertTeam(refundRatePercent, 3);
}Common Pitfalls
- Skipping webhook signature verification. Always validate with
stripe.webhooks.constructEvent()—otherwise malicious actors can send fake refund events to your endpoint. - Forgetting pagination. Stripe returns max 100 items per API call. If you have thousands of charges, you'll only see the first 100. Use the
has_moreflag to loop through all results. - Comparing absolute refund amounts instead of rates. A $50 refund on $500 in sales (10%) is worse than $50 on $5000 (1%). Always calculate percentage.
- Alerting too often. Checking every minute and notifying on every spike creates alert fatigue. Check hourly or daily and set a window—e.g., only alert if rate is elevated for 2 consecutive checks.
Wrapping Up
You now have real-time visibility into your Stripe refund rate and automatic notifications when it spikes. The moment an alert fires, your team can jump into the Dashboard to investigate the root cause—whether it's a specific product, cohort, or payment method. If you want to track refund rate automatically alongside other metrics like churn, conversion, and revenue across all your tools, Product Analyst can help.