# How to handle chargebacks in Stripe

- Tool: Stripe
- Difficulty: Intermediate
- Time required: 20 minutes
- Compatibility: Stripe API v2024-12+, Node.js 18+, Stripe Dashboard
- Last updated: March 2026

## 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.

## Before you start

- 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.

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

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

**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.

```
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) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case 'charge.dispute.created':
      const dispute = event.data.object;
      console.log(`New dispute: ${dispute.id}, Amount: $${dispute.amount / 100}`);
      console.log(`Reason: ${dispute.reason}, Deadline: ${dispute.evidence_details.due_by}`);
      // Alert your team, gather evidence automatically
      break;
    case 'charge.dispute.closed':
      const closedDispute = event.data.object;
      console.log(`Dispute ${closedDispute.id} closed: ${closedDispute.status}`);
      // Status: won, lost, or warning_closed
      break;
  }

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

**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.

```
// Create a charge that will be disputed:
const paymentIntent = await stripe.paymentIntents.create({
  amount: 10000, // $100.00
  currency: 'usd',
  payment_method: 'pm_card_createDispute', // triggers dispute
  confirm: true,
  automatic_payment_methods: {
    enabled: true,
    allow_redirects: 'never',
  },
});

// 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 code example

File: `dispute-handler.js`

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

const app = express();

// Webhook handler
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.STRIPE_WEBHOOK_SECRET
      );
    } catch (err) {
      return res.status(400).send(`Webhook Error: ${err.message}`);
    }

    switch (event.type) {
      case 'charge.dispute.created': {
        const dispute = event.data.object;
        console.log(`Dispute created: ${dispute.id}`);
        console.log(`Amount: $${dispute.amount / 100}`);
        console.log(`Reason: ${dispute.reason}`);
        const deadline = new Date(dispute.evidence_details.due_by * 1000);
        console.log(`Evidence due by: ${deadline.toISOString()}`);
        // TODO: alert team, auto-gather evidence
        break;
      }
      case 'charge.dispute.closed': {
        const result = event.data.object;
        console.log(`Dispute ${result.id} closed: ${result.status}`);
        if (result.status === 'won') {
          console.log('Funds and $15 fee returned.');
        } else if (result.status === 'lost') {
          console.log('Funds lost. Consider process improvements.');
        }
        break;
      }
    }

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

app.use(express.json());

// Submit evidence for a dispute
app.post('/api/disputes/:disputeId/evidence', async (req, res) => {
  try {
    const { disputeId } = req.params;
    const { evidence, submit } = req.body;

    const dispute = await stripe.disputes.update(disputeId, {
      evidence,
      submit: submit || false,
    });

    res.json({
      id: dispute.id,
      status: dispute.status,
      evidenceSubmitted: submit,
    });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => console.log(`Server on port ${PORT}`));
```

## Common mistakes

- **Not responding to disputes before the deadline** — undefined Fix: You automatically lose if you miss the evidence deadline. Set up charge.dispute.created webhooks and alert your team immediately.
- **Submitting weak or generic evidence** — undefined Fix: Include specific evidence: delivery tracking, access logs, signed agreements, customer communication, and your refund policy. Generic statements rarely win.
- **Refunding a charge after a dispute is filed** — undefined Fix: 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.
- **Ignoring dispute rate monitoring** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-handle-chargebacks-in-stripe
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-handle-chargebacks-in-stripe
