# Handling disputes

> List, inspect, and respond to payment disputes (chargebacks) with the Qualy API.

A dispute (also called a chargeback) happens when a cardholder questions a payment with their bank. When a dispute is opened, the disputed amount is typically withheld until it's resolved. This guide covers how to find disputes and respond to them with evidence.

---

## Before you begin

You don't **create** disputes — Qualy opens them automatically when a payment provider notifies us of a chargeback, and links each one to the original transaction, payment intent, and contact. Your job is to detect open disputes and respond in time.

> **Note — Disputes are time-sensitive**
>
> A dispute in `needs-response` (or `warning-needs-response`) has a deadline. Submit your evidence before it passes, or the dispute is decided against you.

---

## Dispute lifecycle

A dispute's `status` tells you where it stands and whether it still needs you:

| Status | Meaning |
| --- | --- |
| `warning-needs-response` | An early warning was raised. You can respond, but no funds have been withdrawn yet. |
| `warning-under-review` | Your response to a warning is being reviewed. |
| `warning-closed` | The warning was resolved and closed. **Closed.** |
| `needs-response` | A formal dispute is open and requires your evidence before the deadline. |
| `under-review` | Your evidence has been submitted and the bank is reviewing it. |
| `won` | Resolved in your favor — funds are retained. **Closed.** |
| `lost` | Resolved against you — funds are returned to the cardholder. **Closed.** |

`won`, `lost`, and `warning-closed` are the closed states; everything else is an open dispute. You can only refund a transaction once any dispute on it is closed.

### Dispute reasons

The `reason` field explains why the cardholder disputed the payment. Possible values: `fraudulent`, `unrecognized`, `duplicate`, `credit-not-processed`, `debit-not-authorized`, `customer-initiated`, `bank-cannot-process`, `incorrect-account-details`, `insufficient-funds`, `general`, and `other`. Tailor your evidence to the reason.

---

## List disputes

Fetch disputes with the standard [query parameters](/docs/queries.md). Filter by `status` to find the ones that need action. The array is returned under `data.disputes`:

**cURL**

```bash
curl "https://api.qualyhq.com/v1/disputes?status[0]=needs-response&status[1]=warning-needs-response" \
  -H 'Authorization: ApiKey your-api-key-here' \
  -H 'X-TENANT-ID: your-tenant-id-here'
```

**JavaScript**

```javascript
const params = new URLSearchParams();
params.append('status[0]', 'needs-response');
params.append('status[1]', 'warning-needs-response');

const res = await fetch(`https://api.qualyhq.com/v1/disputes?${params}`, {
  headers: {
    'Authorization': 'ApiKey your-api-key-here',
    'X-TENANT-ID': 'your-tenant-id-here',
  },
});

const { data } = await res.json();
console.log(data.disputes); // array of open disputes
```

**PHP (Laravel)**

```php
use Illuminate\Support\Facades\Http;

$disputes = Http::withHeaders([
    'Authorization' => 'ApiKey your-api-key-here',
    'X-TENANT-ID' => 'your-tenant-id-here',
])->get('https://api.qualyhq.com/v1/disputes', [
    'status' => ['needs-response', 'warning-needs-response'],
])->json('data.disputes');
```

Retrieve a single dispute with `GET /v1/disputes/{disputeId}`. The dispute object references the `transaction`, `paymentIntent`, and `contact` it relates to, plus its `amount`, `currency`, `status`, and `reason`.

---

## Respond with evidence

Responding takes two steps: **upload each evidence file**, then **submit** the dispute with those files attached.

### 1. Upload your evidence files

Upload each supporting document (receipts, delivery proof, customer communication) through the [Storage API](https://v1-spec.qualyhq.com/#post-/v1/storage/files/upload/sign) and keep the resulting file ID.

### 2. Submit the dispute

Send a `POST` to `/v1/disputes/{disputeId}/update` with `submit: true` and an `evidence` array. Each evidence item references an uploaded `document` (its storage file ID), a `type`, and optional `comments`:

**cURL**

```bash
curl -X POST https://api.qualyhq.com/v1/disputes/653fc551d14abfe63d4fd48b/update \
  -H 'Authorization: ApiKey your-api-key-here' \
  -H 'X-TENANT-ID: your-tenant-id-here' \
  -H 'Content-Type: application/json' \
  -d '{
    "submit": true,
    "evidence": [
      {
        "document": "507f1f77bcf86cd799439011",
        "type": "service-documentation",
        "comments": "Signed enrollment agreement and course access logs."
      }
    ]
  }'
```

**JavaScript**

```javascript
const res = await fetch(
  'https://api.qualyhq.com/v1/disputes/653fc551d14abfe63d4fd48b/update',
  {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': 'ApiKey your-api-key-here',
      'X-TENANT-ID': 'your-tenant-id-here',
    },
    body: JSON.stringify({
      submit: true,
      evidence: [
        {
          document: '507f1f77bcf86cd799439011',
          type: 'service-documentation',
          comments: 'Signed enrollment agreement and course access logs.',
        },
      ],
    }),
  },
);

const { data: dispute } = await res.json();
console.log(dispute.status); // "under-review"
```

**PHP (Laravel)**

```php
use Illuminate\Support\Facades\Http;

$dispute = Http::withHeaders([
    'Authorization' => 'ApiKey your-api-key-here',
    'X-TENANT-ID' => 'your-tenant-id-here',
])->post('https://api.qualyhq.com/v1/disputes/653fc551d14abfe63d4fd48b/update', [
    'submit' => true,
    'evidence' => [
        [
            'document' => '507f1f77bcf86cd799439011',
            'type' => 'service-documentation',
            'comments' => 'Signed enrollment agreement and course access logs.',
        ],
    ],
])->json('data');

echo $dispute['status']; // "under-review"
```

The `type` of each evidence item must be one of `service-documentation`, `customer-communication`, `activity-log`, or `uncategorized-file`. After a successful submit, the dispute moves to `under-review`.

> **Note — Saving a draft vs. submitting**
>
> Set `submit: false` to attach evidence without sending it to the bank yet — useful for assembling your response over time. Once you set `submit: true`, the evidence is final and the dispute is handed off for review.

### Conceding a dispute

If you don't intend to contest the charge, concede it by sending `status: "lost"`. This closes the dispute and returns the funds to the cardholder — do this instead of ignoring a dispute you can't win, to avoid unnecessary fees.

---

## Refunds and disputes

You cannot refund a transaction while a dispute on it is open. Refunds are only allowed once the dispute reaches a closed state (`won`, `lost`, or `warning-closed`). See [Issuing refunds](/docs/guides/issuing-transaction-refunds.md).

---

## Next steps

- Reconcile disputes against the original payment with [Querying data](/docs/queries.md).
- Learn how funds move in [payment splits](/docs/guides/creating-payment-splits.md).
- Handle [errors](/docs/errors.md) from the dispute endpoints.
