Skip to main content
RapidDev - Software Development Agency
stripe-guide

How to handle chargebacks in Stripe

Handle Stripe chargebacks (disputes) by responding promptly through Dashboard → Disputes with compelling evidence. Each dispute carries a $15 fee (returned if you win). The dispute lifecycle is: charge.dispute.created → submit evidence within 7-21 days → bank decides. Prevent chargebacks by using clear statement descriptors, sending receipts, and issuing proactive refunds.

What you'll learn

  • How the chargeback (dispute) lifecycle works in Stripe
  • How to submit evidence to fight a dispute
  • How to handle dispute webhooks programmatically
  • How to prevent chargebacks with best practices
Book a free consultation
4.9Clutch rating
600+Happy partners
17+Countries served
190+Team members
Intermediate6 min read20 minutesStripe API v2024-12+, Node.js 18+, Stripe DashboardMarch 2026RapidDev Engineering Team
TL;DR

Handle Stripe chargebacks (disputes) by responding promptly through Dashboard → Disputes with compelling evidence. Each dispute carries a $15 fee (returned if you win). The dispute lifecycle is: charge.dispute.created → submit evidence within 7-21 days → bank decides. Prevent chargebacks by using clear statement descriptors, sending receipts, and issuing proactive refunds.

Understanding Chargebacks and Disputes in Stripe

A chargeback (Stripe calls it a dispute) happens when a customer contests a charge with their bank. The bank pulls the funds from your account, charges a $15 dispute fee, and gives you a window to submit evidence proving the charge was legitimate. If you win, the funds and fee are returned. If you lose (or do not respond), the customer keeps the money. High dispute rates above 0.75% can trigger card network monitoring programs and potentially account closure.

Prerequisites

  • A Stripe account (disputes can be simulated in test mode)
  • Understanding of your product delivery and refund policies
  • Node.js 18 or newer installed (for webhook handling)
  • Your Stripe secret key and webhook signing secret

Step-by-step guide

1

Understand the dispute lifecycle

When a customer disputes a charge: (1) The bank notifies Stripe, (2) Stripe deducts the disputed amount + $15 fee from your balance, (3) Stripe creates a Dispute object and sends you an email, (4) You have 7-21 days (depending on card network) to submit evidence, (5) The bank reviews evidence and makes a final decision.

Expected result: You understand the timeline and can plan your response accordingly.

2

View and respond to disputes in the Dashboard

Go to Payments → Disputes in the Stripe Dashboard. Click the dispute to see details including the reason code, disputed amount, and evidence deadline. Click 'Submit evidence' to upload your supporting documents.

Expected result: You submit evidence before the deadline. The dispute status changes to 'under_review'.

3

Submit evidence via the API

For automated dispute handling, use the API to submit evidence programmatically. This is useful when you can automatically gather order details and delivery proof.

typescript
1const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
2
3await stripe.disputes.update('dp_ABC123', {
4 evidence: {
5 product_description: 'Annual SaaS subscription for project management tool',
6 customer_email_address: 'customer@example.com',
7 customer_name: 'Jane Smith',
8 billing_address: '123 Main St, San Francisco, CA 94105',
9 receipt: 'file_RECEIPT123', // uploaded file ID
10 service_date: '2025-01-15',
11 access_activity_log: 'Customer logged in 47 times between Jan 15 - Feb 15, 2025. Last login: Feb 14 at 3:42 PM UTC.',
12 cancellation_policy: 'Full refund within 30 days. Customer disputed after 45 days.',
13 refund_policy: 'file_POLICY456',
14 },
15 submit: true, // Set to true to submit; false to save as draft
16});

Expected result: Evidence is submitted to the bank for review. The dispute status changes to 'under_review'.

4

Handle dispute webhooks

Set up webhook listeners for dispute events to respond programmatically and alert your team immediately when a dispute is created.

typescript
1app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
2 const sig = req.headers['stripe-signature'];
3 let event;
4
5 try {
6 event = stripe.webhooks.constructEvent(
7 req.body,
8 sig,
9 process.env.STRIPE_WEBHOOK_SECRET
10 );
11 } catch (err) {
12 return res.status(400).send(`Webhook Error: ${err.message}`);
13 }
14
15 switch (event.type) {
16 case 'charge.dispute.created':
17 const dispute = event.data.object;
18 console.log(`New dispute: ${dispute.id}, Amount: $${dispute.amount / 100}`);
19 console.log(`Reason: ${dispute.reason}, Deadline: ${dispute.evidence_details.due_by}`);
20 // Alert your team, gather evidence automatically
21 break;
22 case 'charge.dispute.closed':
23 const closedDispute = event.data.object;
24 console.log(`Dispute ${closedDispute.id} closed: ${closedDispute.status}`);
25 // Status: won, lost, or warning_closed
26 break;
27 }
28
29 res.json({ received: true });
30});

Expected result: Your server receives real-time notifications when disputes are created and resolved.

5

Test disputes in test mode

Simulate a dispute in test mode by creating a charge with a special test token, then triggering the dispute via the Stripe CLI or Dashboard.

typescript
1// Create a charge that will be disputed:
2const paymentIntent = await stripe.paymentIntents.create({
3 amount: 10000, // $100.00
4 currency: 'usd',
5 payment_method: 'pm_card_createDispute', // triggers dispute
6 confirm: true,
7 automatic_payment_methods: {
8 enabled: true,
9 allow_redirects: 'never',
10 },
11});
12
13// A dispute will be created automatically on this charge

Expected result: A dispute is created on the test charge, simulating the full dispute lifecycle.

Complete working example

dispute-handler.js
1const express = require('express');
2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
3
4const app = express();
5
6// Webhook handler
7app.post('/webhook',
8 express.raw({ type: 'application/json' }),
9 async (req, res) => {
10 const sig = req.headers['stripe-signature'];
11 let event;
12
13 try {
14 event = stripe.webhooks.constructEvent(
15 req.body,
16 sig,
17 process.env.STRIPE_WEBHOOK_SECRET
18 );
19 } catch (err) {
20 return res.status(400).send(`Webhook Error: ${err.message}`);
21 }
22
23 switch (event.type) {
24 case 'charge.dispute.created': {
25 const dispute = event.data.object;
26 console.log(`Dispute created: ${dispute.id}`);
27 console.log(`Amount: $${dispute.amount / 100}`);
28 console.log(`Reason: ${dispute.reason}`);
29 const deadline = new Date(dispute.evidence_details.due_by * 1000);
30 console.log(`Evidence due by: ${deadline.toISOString()}`);
31 // TODO: alert team, auto-gather evidence
32 break;
33 }
34 case 'charge.dispute.closed': {
35 const result = event.data.object;
36 console.log(`Dispute ${result.id} closed: ${result.status}`);
37 if (result.status === 'won') {
38 console.log('Funds and $15 fee returned.');
39 } else if (result.status === 'lost') {
40 console.log('Funds lost. Consider process improvements.');
41 }
42 break;
43 }
44 }
45
46 res.json({ received: true });
47 }
48);
49
50app.use(express.json());
51
52// Submit evidence for a dispute
53app.post('/api/disputes/:disputeId/evidence', async (req, res) => {
54 try {
55 const { disputeId } = req.params;
56 const { evidence, submit } = req.body;
57
58 const dispute = await stripe.disputes.update(disputeId, {
59 evidence,
60 submit: submit || false,
61 });
62
63 res.json({
64 id: dispute.id,
65 status: dispute.status,
66 evidenceSubmitted: submit,
67 });
68 } catch (err) {
69 res.status(500).json({ error: err.message });
70 }
71});
72
73const PORT = process.env.PORT || 3000;
74app.listen(PORT, () => console.log(`Server on port ${PORT}`));

Common mistakes when handling chargebacks in Stripe

Why it's a problem: Not responding to disputes before the deadline

How to avoid: You automatically lose if you miss the evidence deadline. Set up charge.dispute.created webhooks and alert your team immediately.

Why it's a problem: Submitting weak or generic evidence

How to avoid: Include specific evidence: delivery tracking, access logs, signed agreements, customer communication, and your refund policy. Generic statements rarely win.

Why it's a problem: Refunding a charge after a dispute is filed

How to avoid: Once a dispute is created, do not issue a refund — it complicates the dispute process. Accept the dispute instead if you agree the customer should get their money back.

Why it's a problem: Ignoring dispute rate monitoring

How to avoid: Card networks flag accounts with dispute rates above 0.75%. Monitor your rate in Dashboard → Analytics and address root causes proactively.

Best practices

  • Respond to every dispute — even if you expect to lose, responding demonstrates good faith
  • Set up charge.dispute.created webhooks to get immediate notification
  • Use clear statement descriptors so customers recognize charges on their bank statements
  • Send email receipts for every payment to create a paper trail
  • Issue proactive refunds for unhappy customers before they dispute — a refund is cheaper than a dispute
  • Keep detailed records of orders, deliveries, and customer communications
  • Monitor your dispute rate and investigate recurring dispute reasons
  • Use Stripe Radar to block suspicious payments before they result in disputes

Still stuck?

Copy one of these prompts to get a personalized, step-by-step explanation.

ChatGPT Prompt

Write a Node.js webhook handler for Stripe dispute events. Handle charge.dispute.created to log dispute details and deadline, and charge.dispute.closed to log the outcome. Use stripe.webhooks.constructEvent with raw body for signature verification.

Stripe Prompt

Add chargeback handling to my app. Set up a webhook to receive dispute notifications, auto-gather order evidence, and create an endpoint to submit dispute evidence via the Stripe API. Include deadline tracking.

Frequently asked questions

How much does a Stripe dispute cost?

Each dispute carries a $15 non-refundable fee. If you win the dispute, the $15 fee is returned along with the disputed amount. If you lose, you forfeit both.

What is the average dispute win rate?

Across the industry, merchants win about 20-30% of disputes. With strong evidence (delivery confirmation, access logs, signed agreements), win rates can reach 50%+.

Can I prevent disputes entirely?

No, but you can significantly reduce them. Use clear statement descriptors, send receipts, offer easy refunds, respond to customer complaints quickly, and use Stripe Radar for fraud prevention.

What happens if my dispute rate is too high?

Card networks (Visa, Mastercard) place merchants with dispute rates above 0.75% into monitoring programs. Continued high rates can result in fines, increased processing fees, or account termination.

Can I appeal a lost dispute?

No. Dispute decisions are final. Once the issuing bank makes a decision, it cannot be reversed through Stripe. Some card networks allow a second representment in rare cases.

What if I need help building a dispute management and prevention system?

For businesses with high transaction volumes that need automated evidence gathering, dispute analytics, and prevention strategies, the RapidDev team can help build a comprehensive dispute management system.

RapidDev

Talk to an Expert

Our team has built 600+ apps. Get personalized help with your project.

Book a free consultation

Need help with your project?

Our experts have built 600+ apps and can accelerate your development. Book a free consultation — no strings attached.

Book a free consultation

We put the rapid in RapidDev

Need a dedicated strategic tech and growth partner? Discover what RapidDev can do for your business! Book a call with our team to schedule a free, no-obligation consultation. We'll discuss your project and provide a custom quote at no cost.