Skip to content

Stripe Disputes: Handle Chargebacks and Manage Fraud Claims

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about Stripe Disputes: Handle Chargebacks and Manage Fraud Claims. We cover key concepts, practical examples, and best practices to help you master this topic.

Stripe disputes (chargebacks) occur when a cardholder challenges a charge with their bank. Proper dispute handling protects your revenue and merchant reputation.

What You'll Learn

How disputes work, understand the dispute lifecycle, respond with compelling evidence, prevent disputes with Stripe Radar, manage dispute metrics, and build a dispute response system.

Why It Matters

A high dispute rate (above 0.9%) threatens your Stripe account. DodaTech maintains a 0.05% dispute rate through Radar fraud prevention and automated evidence submission.

Real-World Use

A customer claims they didn't authorize a charge. Stripe creates a dispute, notifies your Webhook, and gives you 7 days to submit evidence. Your system automatically collects and submits proof.

flowchart LR
    A["Customer Disputes\nCharge with Bank"] --> B["Stripe Notifies\nWebhook: charge.dispute.created"]
    B --> C["7-Day Window\nto Respond"]
    C --> D["Gather Evidence\nReceipts, Logs, Proof"]
    D --> E["Submit to\nStripe"]
    E --> F{"Outcome?"}
    F -->|"Won"| G["Funds Returned"]
    F -->|"Lost"| H["Charge Stands\n+ $15 Fee"]
    style C fill:#fef3c7,stroke:#d97706
    style E fill:#dbeafe,stroke:#2563eb
    style F fill:#fecaca,stroke:#dc2626

Dispute Webhook Handler

@app.route("/stripe/webhook", methods=["POST"])
def handle_dispute_events():
    event = stripe.Webhook.construct_event(
        request.get_data(), request.headers.get("Stripe-Signature"), endpoint_secret
    )

    event_type = event["type"]
    dispute = event["data"]["object"]

    if event_type == "charge.dispute.created":
        print(f"Dispute created: {dispute['id']}")
        print(f"Amount: ${dispute['amount']/100:.2f}")
        print(f"Reason: {dispute['reason']}")
        print(f"Due date: {dispute['evidence_details']['due_by']}")

        auto_submit_evidence(dispute)
        alert_team(dispute)

    elif event_type == "charge.dispute.updated":
        print(f"Dispute updated: {dispute['id']} (status: {dispute['status']})")

    elif event_type == "charge.dispute.closed":
        print(f"Dispute closed: {dispute['id']} ({dispute['status']})")

    return jsonify({"status": "ok"}), 200

Submitting Evidence

def submit_dispute_evidence(dispute_id, charge_id, customer_email):
    # Gather evidence
    evidence = {
        "customer_name": "Alice Smith",
        "customer_email": customer_email,
        "customer_purchase_ip": "192.168.1.1",
        "shipping_address": "123 Main St, Anytown, US",
        "shipping_tracking_number": "1Z999AA10123456784",
        "receipt": "https://dodatech.com/receipts/ord_12345",
        "service_date": "2026-06-15",
        "service_documentation": "Customer purchased Pro Plan on June 15, 2026. Service was activated and used for 10 days.",
        "uncategorized_text": "Customer logged in 5 times, ran 3 scans, and downloaded reports before disputing.",
    }

    try:
        stripe.Dispute.update(
            dispute_id,
            evidence=evidence,
            metadata={"submitted_automatically": "true"}
        )
        print(f"Evidence submitted for dispute {dispute_id}")
        return True

    except stripe.error.StripeError as e:
        print(f"Evidence submission failed: {e}")
        return False

Preventing Disputes with Radar

# Stripe Radar provides built-in fraud prevention
# Configure rules in Dashboard: Radar > Rules

# Common Radar rules:
# - Block orders with stolen card test (CVC check failed)
# - Block orders from high-risk countries
# - Require 3D Secure for orders over $100
# - Block orders with mismatched IP/billing address

def check_radar_outcome(payment_intent):
    # Access Radar information on a PaymentIntent
    print(f"Risk level: {payment_intent.outcome['risk_level']}")
    print(f"Risk score: {payment_intent.outcome['risk_score']}")
    print(f"Rule: {payment_intent.outcome.get('rule', 'none')}")

    if payment_intent.outcome["risk_level"] == "elevated":
        print("High risk payment — additional verification recommended")
        return "review"
    elif payment_intent.outcome["risk_level"] == "highest":
        print("Highest risk — consider refunding")
        return "refund"

    return "accept"

Common Mistakes

1. Missing the Evidence Window

You have 7 days (sometimes 14) to submit evidence. Late response automatically loses the dispute. Configure reminders for approaching deadlines.

2. Submitting Weak Evidence

A receipt alone isn't enough. Submit evidence that proves the customer received the service: login logs, IP addresses, shipping tracking, communication records.

3. Not Using Radar for Prevention

Radar blocks most fraudulent charges before they become disputes. Invest time in configuring Radar rules rather than reactively handling disputes.

4. Ignoring Dispute Patterns

If the same customer disputes multiple charges, it may indicate friendly fraud. Flag repeated disputers and require manual review.

5. Refunding Disputed Charges

Never refund a disputed charge from your side. Handle it through the dispute Process. Refunding doesn't cancel the dispute.

Practice Questions

  1. What is the dispute response window?
  2. What evidence should you submit for a digital service dispute?
  3. How does Stripe Radar help prevent disputes?
  4. What happens if you lose a dispute?

Answers:

  1. 7 days from dispute creation. Check dispute.evidence_details.due_by for the exact deadline.
  2. Service documentation (what was purchased), customer communication logs, IP addresses, login timestamps, proof of delivery (API calls, downloads).
  3. Radar analyzes transactions in real-time, blocking high-risk charges and requiring 3D Secure for suspicious ones before they become disputes.
  4. The disputed amount is debited from your account, plus a $15 dispute fee. Your dispute rate increases, which can threaten your account.

Challenge: Build a dispute management system: listen for dispute.created webhook, auto-collect evidence (receipts, logs, shipping), submit evidence via API, track dispute deadlines with reminders, configure Radar rules for prevention, and report dispute metrics monthly.

FAQ

What is a chargeback?

A chargeback occurs when a cardholder disputes a charge with their bank. The bank reverses the transaction, debiting the merchant's account.

How much does a dispute cost?

Stripe charges a $15 dispute fee, which is not refunded if you win. The disputed amount is also temporarily held.

What is the difference between a dispute and a refund?

A refund is initiated by you voluntarily. A dispute is initiated by the customer's bank involuntarily. You should never refund a disputed charge.

Can I fight a dispute?

Yes, submit evidence within the response window. Stripe reviews and forwards it to the bank. Strong evidence (receipts, logs, shipping) improves your chances.

What dispute rate is acceptable?

Stripe requires a dispute rate below 0.9% (9 per 1000 transactions). Above this threshold risks account suspension.

Mini Project

Build a dispute prevention and response system: configure Radar rules (block high-risk, require 3D Secure over $100), auto-submit evidence on dispute creation, track deadlines with daily reminders, monitor dispute rate with alerts at 0.5% threshold, and generate monthly dispute reports.

What's Next

Coupons & Promotions — create and manage discount codes.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro