Learn how to manage Stripe disputes step-by-step: set up notifications, gather evidence, respond via dashboard or API, track status, and prevent future chargebacks.
Book a call with an Expert
Starting a new venture? Need to upgrade your web app? RapidDev builds application with your growth in mind.
How to Manage Disputes in Stripe: A Comprehensive Guide
Disputes (often called chargebacks) occur when a customer questions a charge with their bank or credit card company. Managing disputes effectively is crucial for businesses using Stripe as their payment processor. This tutorial will walk you through handling disputes in Stripe from beginning to end.
Step 1: Understanding Stripe Disputes
Before diving into the management process, it's important to understand what disputes are in Stripe:
Step 2: Setting Up Dispute Notifications
Ensure you're promptly notified about disputes:
Here's how to set up a webhook for dispute events:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
const express = require('express');
const app = express();
app.post('/webhook', express.raw({type: 'application/json'}), (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
request.body,
sig,
'whsec_YOUR_WEBHOOK\_SECRET'
);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle dispute events
if (event.type === 'charge.dispute.created') {
const dispute = event.data.object;
console.log(`New dispute received with ID: ${dispute.id}`);
// Add your dispute handling logic here
}
if (event.type === 'charge.dispute.updated') {
const dispute = event.data.object;
console.log(`Dispute updated: ${dispute.id}, Status: ${dispute.status}`);
// Add your update handling logic here
}
response.status(200).send({received: true});
});
app.listen(3000, () => console.log('Running on port 3000'));
Step 3: Accessing Dispute Information
When a dispute occurs, you need to access its details:
Alternatively, use the API to retrieve dispute information:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
// Retrieve a specific dispute
async function getDisputeDetails(disputeId) {
try {
const dispute = await stripe.disputes.retrieve(disputeId);
console.log('Dispute details:', dispute);
return dispute;
} catch (error) {
console.error('Error retrieving dispute:', error);
throw error;
}
}
// List all disputes
async function listDisputes() {
try {
const disputes = await stripe.disputes.list({
limit: 10,
// Optional filters
// created: { gte: Math.floor(Date.now() / 1000) - 86400 }, // Last 24 hours
// status: 'needs\_response'
});
console.log('Disputes:', disputes.data);
return disputes;
} catch (error) {
console.error('Error listing disputes:', error);
throw error;
}
}
// Execute functions
getDisputeDetails('dp\_123456789');
listDisputes();
Step 4: Understanding Dispute Types and Evidence Requirements
Different dispute types require different evidence. Here are the common categories:
Step 5: Preparing Your Evidence
Gather evidence based on the dispute type. Common evidence includes:
Step 6: Submitting Your Dispute Response
Submit your evidence through the Stripe Dashboard:
Alternatively, use the API to submit evidence:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
async function submitDisputeEvidence(disputeId) {
try {
const dispute = await stripe.disputes.update(
disputeId,
{
evidence: {
customer\_name: 'John Smith',
customer\_email: '[email protected]',
customer_purchase_ip: '192.168.0.1',
customer\_signature: 'https://example.com/signature.png',
billing\_address: '123 Main St, Anytown, AT 12345',
receipt: 'https://example.com/receipt.pdf',
service\_date: '2023-01-15',
shipping\_address: '123 Main St, Anytown, AT 12345',
shipping\_date: '2023-01-16',
shipping\_carrier: 'USPS',
shipping_tracking_number: '1Z999AA10123456784',
product\_description: 'High-quality merchandise as described on website',
uncategorized\_text: 'Additional evidence information that doesn't fit into other categories'
},
submit: true // Set to true to submit evidence immediately
}
);
console.log('Evidence submitted successfully:', dispute.status);
return dispute;
} catch (error) {
console.error('Error submitting evidence:', error);
throw error;
}
}
// Execute the function
submitDisputeEvidence('dp\_123456789');
Step 7: Tracking Dispute Status
After submitting evidence, monitor the dispute status:
You can check the status via the dashboard or API:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
async function checkDisputeStatus(disputeId) {
try {
const dispute = await stripe.disputes.retrieve(disputeId);
console.log(`Dispute status: ${dispute.status}`);
console.log(`Dispute amount: ${dispute.amount / 100} ${dispute.currency}`);
console.log(`Evidence due by: ${new Date(dispute.evidence_details.due_by * 1000).toLocaleDateString()}`);
return dispute;
} catch (error) {
console.error('Error checking dispute status:', error);
throw error;
}
}
// Execute the function
checkDisputeStatus('dp\_123456789');
Step 8: Implementing Preventative Measures
To reduce future disputes, implement these preventative measures:
Here's how to enable Radar rules and 3D Secure:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
// Create a PaymentIntent with 3D Secure authentication
async function createSecurePayment(amount, currency, customerId) {
try {
const paymentIntent = await stripe.paymentIntents.create({
amount: amount,
currency: currency,
customer: customerId,
payment_method_types: ['card'],
setup_future_usage: 'off\_session',
confirmation\_method: 'automatic',
confirm: true,
return\_url: 'https://example.com/return',
payment_method_options: {
card: {
request_three_d\_secure: 'any' // Options: 'automatic', 'any', 'required'
}
},
metadata: {
order_id: 'ORDER_12345',
customer\_name: 'John Smith'
}
});
console.log('Payment intent created:', paymentIntent.id);
return paymentIntent;
} catch (error) {
console.error('Error creating secure payment:', error);
throw error;
}
}
// Execute the function
createSecurePayment(2000, 'usd', 'cus\_123456789');
Step 9: Analyzing Dispute Patterns
Regularly analyze your disputes to identify patterns:
Use the Stripe API to gather dispute analytics:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
async function analyzeDisputePatterns() {
try {
// Get all disputes from the last 90 days
const endDate = Math.floor(Date.now() / 1000);
const startDate = endDate - (90 _ 24 _ 60 \* 60);
const disputes = await stripe.disputes.list({
limit: 100,
created: {
gte: startDate,
lte: endDate
},
expand: ['data.charge']
});
// Basic analysis
const totalDisputes = disputes.data.length;
const disputesByReason = {};
const disputesByStatus = {};
const disputesByProduct = {};
disputes.data.forEach(dispute => {
// Count by reason
const reason = dispute.reason || 'unknown';
disputesByReason[reason] = (disputesByReason[reason] || 0) + 1;
// Count by status
const status = dispute.status || 'unknown';
disputesByStatus[status] = (disputesByStatus[status] || 0) + 1;
// Count by product (if you store product info in metadata)
if (dispute.charge && dispute.charge.metadata && dispute.charge.metadata.product\_id) {
const productId = dispute.charge.metadata.product\_id;
disputesByProduct[productId] = (disputesByProduct[productId] || 0) + 1;
}
});
console.log('Dispute Analysis:');
console.log('Total Disputes:', totalDisputes);
console.log('By Reason:', disputesByReason);
console.log('By Status:', disputesByStatus);
console.log('By Product:', disputesByProduct);
return {
totalDisputes,
disputesByReason,
disputesByStatus,
disputesByProduct
};
} catch (error) {
console.error('Error analyzing disputes:', error);
throw error;
}
}
// Execute the function
analyzeDisputePatterns();
Step 10: Automating Dispute Management
For businesses with high transaction volume, automate parts of the dispute management process:
Here's an example of automated dispute handling with Node.js:
const stripe = require('stripe')('sk_test_YOUR_SECRET_KEY');
const express = require('express');
const app = express();
// Your database or order system integration
const orderSystem = {
getOrderDetails: async (orderId) => {
// Mock implementation - replace with your actual order system
return {
id: orderId,
customer: {
name: 'John Smith',
email: '[email protected]',
ip: '192.168.1.1'
},
shipping: {
carrier: 'FedEx',
trackingNumber: '123456789012',
address: '123 Main St, Anytown, AT 12345',
deliveryDate: '2023-01-20'
},
product: {
description: 'Premium Widget X1000',
details: 'High-quality widget with 1-year warranty'
},
receipt\_url: 'https://example.com/receipts/order-123.pdf',
date: '2023-01-15'
};
}
};
app.post('/webhook', express.raw({type: 'application/json'}), async (request, response) => {
const sig = request.headers['stripe-signature'];
let event;
try {
event = stripe.webhooks.constructEvent(
request.body,
sig,
'whsec_YOUR_WEBHOOK\_SECRET'
);
} catch (err) {
return response.status(400).send(`Webhook Error: ${err.message}`);
}
// Handle dispute creation
if (event.type === 'charge.dispute.created') {
const dispute = event.data.object;
console.log(`New dispute received with ID: ${dispute.id}`);
try {
// Get charge information to find the associated order
const charge = await stripe.charges.retrieve(dispute.charge);
// Assuming you store the order ID in charge metadata
if (charge.metadata && charge.metadata.order\_id) {
const orderId = charge.metadata.order\_id;
// Get order details from your system
const orderDetails = await orderSystem.getOrderDetails(orderId);
// Prepare evidence based on dispute reason
let evidenceParams = {
customer\_name: orderDetails.customer.name,
customer\_email: orderDetails.customer.email,
customer_purchase_ip: orderDetails.customer.ip,
receipt: orderDetails.receipt\_url,
service\_date: orderDetails.date,
product\_description: orderDetails.product.description
};
// Add specific evidence based on dispute reason
if (dispute.reason === 'product_not_received') {
evidenceParams = {
...evidenceParams,
shipping\_carrier: orderDetails.shipping.carrier,
shipping_tracking_number: orderDetails.shipping.trackingNumber,
shipping\_address: orderDetails.shipping.address,
shipping\_date: orderDetails.date
};
} else if (dispute.reason === 'product\_unacceptable') {
evidenceParams = {
...evidenceParams,
product\_description: `${orderDetails.product.description}: ${orderDetails.product.details}`
};
}
// Submit the evidence
const updatedDispute = await stripe.disputes.update(
dispute.id,
{
evidence: evidenceParams,
submit: true // Automatically submit the evidence
}
);
console.log(`Automated response submitted for dispute ${dispute.id}`);
} else {
console.log(`Order ID not found for dispute ${dispute.id}, manual handling required`);
// Implement notification for manual handling
}
} catch (error) {
console.error('Error handling dispute:', error);
}
}
response.status(200).send({received: true});
});
app.listen(3000, () => console.log('Running on port 3000'));
Conclusion
Managing disputes in Stripe requires a systematic approach from prevention to resolution. By following these steps and implementing best practices, you can reduce the frequency of disputes, improve your chances of winning legitimate challenges, and minimize the impact on your business. Remember to keep detailed records, respond promptly, and continuously analyze patterns to refine your dispute management strategy.
When it comes to serving you, we sweat the little things. That’s why our work makes a big impact.