# How to manage disputes 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

Manage Stripe disputes from Dashboard → Payments → Disputes where you can view details, submit evidence, and track outcomes. Enable Smart Disputes for AI-assisted evidence gathering. Each dispute has a deadline (7-21 days), a $15 fee, and requires specific evidence matching the dispute reason code. Organize your evidence around the reason code for the best chance of winning.

## Managing Disputes Effectively in Stripe

When a customer files a dispute with their bank, you need a structured process to respond. The Stripe Dashboard provides a dedicated Disputes section showing all open, under review, won, and lost disputes. Each dispute includes the reason code (fraudulent, product_not_received, duplicate, etc.), the evidence deadline, and a form to submit your response. Smart Disputes, a Stripe feature, can automatically submit evidence for clear-cut cases. For everything else, matching your evidence to the specific reason code is the key to winning.

## Before you start

- A Stripe account with Dashboard access (admin or analyst role)
- Understanding of your order fulfillment and customer service processes
- Node.js 18 or newer (for API evidence submission)
- Your Stripe API keys from Dashboard → Developers → API keys

## Step-by-step guide

### 1. Access the Disputes Dashboard

In the Stripe Dashboard, go to Payments → Disputes (or use the top search bar to find a specific dispute). The disputes list shows status, amount, reason, and deadline for each dispute. Filter by status: needs response, under review, won, or lost.

**Expected result:** You see all disputes organized by status with deadlines clearly visible.

### 2. Understand dispute reason codes

Each dispute has a reason code that tells you what the customer is claiming. Tailor your evidence to directly address the specific reason.

```
// Dispute reason codes and what evidence to provide:

// 'fraudulent' — Customer claims they didn't authorize the charge
// Evidence: AVS/CVC match, IP address, shipping address matching billing

// 'product_not_received' — Customer says item never arrived
// Evidence: Shipping tracking, delivery confirmation, signed receipt

// 'duplicate' — Customer charged twice for same thing
// Evidence: Proof charges were for different products/services

// 'subscription_canceled' — Customer says they canceled
// Evidence: Cancellation policy, proof subscription was active

// 'credit_not_processed' — Customer says refund was promised but not given
// Evidence: Refund policy, communication showing no refund was agreed

// 'product_unacceptable' — Product not as described
// Evidence: Product description, delivery confirmation, customer communication
```

**Expected result:** You know which evidence to gather based on the dispute reason.

### 3. Submit evidence through the Dashboard

Click on a dispute, then click 'Submit evidence'. Fill in the relevant fields based on the reason code. Upload supporting files (PDFs, screenshots). Add a clear summary in the 'Uncategorized text' field explaining why the charge is legitimate. Click 'Submit evidence' — this is final and cannot be edited.

**Expected result:** Evidence is submitted and the dispute moves to 'under review' status.

### 4. Enable Smart Disputes

Go to Settings → Disputes in the Dashboard and enable Smart Disputes. When enabled, Stripe automatically submits evidence for disputes where it has strong data (like delivery tracking from Stripe-connected shipping providers). Smart Disputes handles simple cases so you can focus on complex ones.

**Expected result:** Smart Disputes is enabled. Stripe auto-responds to clear-cut disputes using available data.

### 5. Upload evidence files via the API

For automated workflows, upload evidence files and submit dispute evidence programmatically.

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

// Upload a file as evidence
const file = await stripe.files.create({
  purpose: 'dispute_evidence',
  file: {
    data: fs.readFileSync('/path/to/receipt.pdf'),
    name: 'receipt.pdf',
    type: 'application/pdf',
  },
});

// Submit evidence with the uploaded file
await stripe.disputes.update('dp_ABC123', {
  evidence: {
    receipt: file.id,
    customer_name: 'Jane Smith',
    customer_email_address: 'jane@example.com',
    product_description: 'Annual SaaS subscription — project management tool',
    service_date: '2025-01-15',
    uncategorized_text: 'Customer purchased on Jan 15, 2025. Access logs show 47 logins through Feb 14. Cancellation policy requires 30-day notice. No cancellation request received.',
  },
  submit: true,
});
```

**Expected result:** Evidence file is uploaded and the dispute evidence is submitted via the API.

## Complete code example

File: `manage-disputes.js`

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

const app = express();

// Webhook for dispute events
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}`);
    }

    if (event.type === 'charge.dispute.created') {
      const dispute = event.data.object;
      const deadline = new Date(dispute.evidence_details.due_by * 1000);
      console.log(`ALERT: New dispute ${dispute.id}`);
      console.log(`Reason: ${dispute.reason}, Amount: $${dispute.amount / 100}`);
      console.log(`Evidence deadline: ${deadline.toISOString()}`);
    }

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

app.use(express.json());

// List disputes with filtering
app.get('/api/disputes', async (req, res) => {
  try {
    const { status, limit = 20 } = req.query;
    const params = { limit: parseInt(limit, 10) };

    const disputes = await stripe.disputes.list(params);

    const filtered = status
      ? disputes.data.filter((d) => d.status === status)
      : disputes.data;

    res.json(filtered.map((d) => ({
      id: d.id,
      amount: d.amount / 100,
      currency: d.currency,
      reason: d.reason,
      status: d.status,
      deadline: d.evidence_details.due_by
        ? new Date(d.evidence_details.due_by * 1000).toISOString()
        : null,
    })));
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

// Get dispute detail
app.get('/api/disputes/:id', async (req, res) => {
  try {
    const dispute = await stripe.disputes.retrieve(req.params.id);
    res.json(dispute);
  } 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

- **Submitting evidence that does not address the specific reason code** — undefined Fix: Read the reason code carefully. 'product_not_received' needs delivery proof, not product descriptions. 'fraudulent' needs authorization proof, not shipping receipts.
- **Submitting evidence too late** — undefined Fix: Evidence deadlines are strict (7-21 days). Set up webhooks for charge.dispute.created and create an internal SLA of 48 hours to respond.
- **Editing evidence after clicking Submit** — undefined Fix: Evidence submission is final — you cannot edit or add more after submitting. Use submit: false to save drafts, then submit: true only when complete.
- **Not monitoring overall dispute rate** — undefined Fix: Track your dispute rate (disputes / total charges). Visa and Mastercard flag merchants at 0.75%. Monitor in Dashboard → Analytics.

## Best practices

- Respond to every dispute, even if you think you will lose — it demonstrates good business practices to card networks
- Match evidence specifically to the dispute reason code for the highest win rate
- Enable Smart Disputes to auto-handle straightforward cases
- Set up charge.dispute.created webhooks for immediate team notification
- Use the uncategorized_text field to tell a clear, factual story with dates and specifics
- Upload evidence as PDFs rather than images for clarity and professionalism
- Save evidence drafts (submit: false) before final submission to allow team review
- Track dispute patterns to identify and fix root causes

## Frequently asked questions

### What is Smart Disputes?

Smart Disputes is a Stripe feature that automatically submits evidence for disputes where Stripe has strong supporting data. Enable it in Dashboard → Settings → Disputes. It handles clear-cut cases so you can focus on complex disputes.

### How long do I have to respond to a dispute?

Deadlines vary by card network: Visa gives 20 days, Mastercard gives 45 days, and Amex gives 20 days from the dispute creation date. Always check the evidence_details.due_by field.

### Can I submit evidence more than once?

No. Once you click Submit (or set submit: true via API), the evidence is final. You cannot add or modify it. Use draft mode (submit: false) to prepare your evidence before final submission.

### What is an inquiry vs a dispute?

An inquiry (also called a retrieval request) is a pre-dispute information request from the bank. Responding to inquiries promptly can prevent them from escalating to full disputes.

### How do I test disputes in test mode?

Use the test payment method pm_card_createDispute to create a charge that automatically generates a dispute. You can then practice submitting evidence.

### What if I need a comprehensive dispute management solution?

For businesses processing high volumes that need automated evidence gathering, team workflows, and analytics to reduce dispute rates, the RapidDev team can build a tailored dispute management system.

---

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