Skip to main content
Disputes (chargebacks) occur when a cardholder challenges a charge with their bank. Flex automatically syncs disputes the moment they are filed and exposes a full API for listing, retrieving, submitting evidence, and closing disputes.

Table of Contents


Overview

Cardholder files dispute with their bank


charge.dispute.created → Flex syncs dispute


Partner receives webhook event: charge.dispute.created

       ├─── Partner calls GET /v1/disputes/:id  (inspect dispute)

       ├─── Partner calls POST /v1/disputes/:id (submit evidence)
       │         └─ submit: false  → save draft, do not submit yet
       │         └─ submit: true   → submit evidence immediately

       └─── Partner calls POST /v1/disputes/:id/close (accept loss)
When a dispute is opened, Flex:
  1. Receives the charge.dispute.created event.
  2. Creates a Dispute record linked to your payment_intent_id and charge_id.
  3. Forwards a charge.dispute.created event to your registered webhook endpoint.
You have until evidence_details.due_by to respond. Submitting evidence with submit: true sends it immediately and increments submission_count. You may re-submit with updated evidence before the deadline. Alternatively, POST /v1/disputes/:id/close concedes the dispute immediately.

Authentication

All requests require a bearer token (API key).
Authorization: Bearer fsk_...
Your API key is scoped to your partner account. Disputes for other partners are never visible regardless of the ID used — they return 404.

The Dispute Object

{
  "dispute": {
    "dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
    "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
    "amount": 4999,
    "currency": "usd",
    "status": "needs_response",
    "reason": "fraudulent",
    "is_charge_refundable": false,
    "metadata": {},
    "created_at": "2026-02-10T14:30:00Z",
    "test_mode": false,

    "evidence_details": {
      "due_by": "2026-03-01T23:59:59Z",
      "has_evidence": false,
      "past_due": false,
      "submission_count": 0
    },

    "balance_transactions": [
      {
        "balance_transaction_id": "fbt_01jx3qabc123",
        "amount": -4999,
        "fee": -1500,
        "net": -6499,
        "status": "available",
        "type": "adjustment",
        "created_at": "2026-02-10T15:00:00Z"
      }
    ],

    "payment_method_details": {
      "type": "card",
      "card": {
        "brand": "visa",
        "network_reason_code": "10.4"
      }
    }
  }
}

Field Reference

FieldTypeDescription
dispute_idstringFlex dispute ID (fdsp_ prefix). Use this in all API calls.
payment_intent_idstringFlex payment intent ID (fpi_) linked to this dispute.
charge_idstring | nullFlex charge ID (fch_) linked to this dispute.
amountintegerDisputed amount in cents (e.g. 4999 = $49.99).
currencystringThree-letter ISO currency code (e.g. "usd").
statusstringCurrent dispute status. See Dispute Status Lifecycle.
reasonstringReason code provided by the card network. See Dispute Reason Codes.
is_charge_refundablebooleanWhether the underlying charge can be refunded.
metadataobjectArbitrary key-value metadata.
created_atstringISO 8601 timestamp when the dispute was created.
test_modebooleantrue if this is a test-mode dispute.
evidence_detailsobject | nullEvidence submission deadline and status. See below.
balance_transactionsarrayBalance transactions associated with this dispute (withdrawals and reinstatements). Only populated on single-dispute GET.
payment_method_detailsobject | nullCard details for the disputed payment. Only populated on single-dispute GET.

evidence_details Fields

FieldTypeDescription
due_bystring | nullISO 8601 deadline for submitting evidence. null if no deadline has been set yet.
has_evidencebooleantrue if any evidence has been saved (draft or submitted).
past_duebooleantrue if the submission deadline has passed.
submission_countintegerNumber of times evidence has been submitted.

balance_transactions Fields (per item)

FieldTypeDescription
balance_transaction_idstringFlex balance transaction ID (fbt_ prefix).
amountintegerAmount in cents. Negative = debit from your balance.
feeintegerDispute fee in cents (charged by the card network).
netintegerNet impact on your balance (amount + fee).
statusstringpending or available.
typestring"adjustment" for dispute debit or reinstatement credit.
created_atstring | nullISO 8601 timestamp.

payment_method_details.card Fields

FieldTypeDescription
brandstring | nullCard brand: "visa", "mastercard", "amex", "discover", etc.
network_reason_codestring | nullCard network reason code (e.g. "10.4" for Visa, "4853" for Mastercard).

Dispute Status Lifecycle

needs_response  ──submit evidence──►  under_review  ──►  won
      │                                                    │
      │                                                    │
      └─────────close (accept loss)──►  lost  ◄───────────┘
StatusDescriptionCan Submit EvidenceCan Close
needs_responseDispute is open and awaiting your response.
warning_needs_responseInquiry (pre-chargeback) needing response.
under_reviewEvidence submitted; issuer reviewing.
warning_under_reviewInquiry under review.
wonDispute resolved in your favour.
lostDispute resolved against you. Funds forfeited.
warning_closedInquiry closed without becoming a chargeback.
Attempting to submit evidence or close a dispute in a non-actionable status returns 422.

Dispute Reason Codes

ValueDescription
fraudulentCustomer claims they did not authorise the charge. Most common.
product_not_receivedCustomer claims they did not receive the product/service.
product_unacceptableCustomer claims the product/service was defective or not as described.
credit_not_processedCustomer claims a refund they were promised was never processed.
duplicateCustomer claims they were charged twice for the same transaction.
subscription_canceledCustomer claims they were charged after cancelling a subscription.
unrecognizedCustomer does not recognise the charge.
bank_cannot_processBank returned the charge for technical reasons.
debit_not_authorizedCustomer claims the debit was not authorised.
customer_initiatedCustomer-initiated dispute.
check_returnedCheck was returned.
incorrect_account_detailsAccount details were incorrect.
insufficient_fundsInsufficient funds.
generalGeneral / uncategorised reason.

Endpoints

List Disputes

GET /v1/disputes
Returns disputes for your account in reverse chronological order (newest first). Query Parameters
ParameterTypeDescription
limitintegerNumber of disputes to return. Default 20, max 100.
starting_afterstringCursor for forward pagination. Pass the last dispute_id from the previous page.
ending_beforestringCursor for backward pagination. Pass the first dispute_id from the current page.
chargestringFilter by Flex charge ID (fch_...).
payment_intentstringFilter by Flex payment intent ID (fpi_...).
Note: balance_transactions and payment_method_details are not populated in list responses. Use GET /v1/disputes/:id to get the full dispute.
Example — list all disputes
GET /v1/disputes?limit=20
Authorization: Bearer fsk_...
Response
{
  "disputes": [
    {
      "dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
      "amount": 4999,
      "currency": "usd",
      "status": "needs_response",
      "reason": "fraudulent",
      "created_at": "2026-02-10T14:30:00Z",
      "evidence_details": {
        "due_by": "2026-03-01T23:59:59Z",
        "has_evidence": false,
        "past_due": false,
        "submission_count": 0
      },
      "balance_transactions": null,
      "payment_method_details": null,
      ...
    }
  ]
}
Example — filter by payment intent
GET /v1/disputes?payment_intent=fpi_01jx3q7ab1c8d2e3f4g5h6j7k8
Authorization: Bearer fsk_...
Example — paginate forward
GET /v1/disputes?limit=10&starting_after=fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...

Get Dispute

GET /v1/disputes/:dispute_id
Retrieves a single dispute by its Flex ID. This is the only endpoint that populates balance_transactions and payment_method_details. Path Parameters
ParameterDescription
dispute_idFlex dispute ID (fdsp_...).
Example
GET /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
Response
{
  "dispute": {
    "dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
    "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
    "amount": 4999,
    "currency": "usd",
    "status": "needs_response",
    "reason": "fraudulent",
    "is_charge_refundable": false,
    "metadata": {},
    "created_at": "2026-02-10T14:30:00Z",
    "test_mode": false,
    "evidence_details": {
      "due_by": "2026-03-01T23:59:59Z",
      "has_evidence": false,
      "past_due": false,
      "submission_count": 0
    },
    "balance_transactions": [
      {
        "balance_transaction_id": "fbt_01jx3qabc123",
        "amount": -4999,
        "fee": -1500,
        "net": -6499,
        "status": "available",
        "type": "adjustment",
        "created_at": "2026-02-10T15:00:00Z"
      }
    ],
    "payment_method_details": {
      "type": "card",
      "card": {
        "brand": "visa",
        "network_reason_code": "10.4"
      }
    }
  }
}
Errors
StatusWhen
404Dispute not found or belongs to a different partner.

Update Evidence

POST /v1/disputes/:dispute_id
Saves evidence for a dispute. Set submit: true to submit evidence immediately. Set submit: false (or omit it) to save a draft without submitting. The dispute must be in needs_response or warning_needs_response status. Path Parameters
ParameterDescription
dispute_idFlex dispute ID (fdsp_...).
Request Body
{
  "dispute": {
    "evidence": { ... },
    "submit": true
  }
}

Evidence Fields

All fields are optional. Include only what is relevant to your dispute reason. Text fields
FieldDescription
customer_nameFull name of the customer as it appears in your records.
customer_email_addressEmail address of the customer.
customer_purchase_ipIP address the customer used when making the purchase.
billing_addressBilling address as entered during checkout.
product_descriptionDescription of the product or service. Be specific.
service_dateDate the service was provided (YYYY-MM-DD).
shipping_addressAddress where the product was shipped.
shipping_carrierCarrier name (e.g. "UPS", "FedEx").
shipping_dateDate the product was shipped (YYYY-MM-DD).
shipping_tracking_numberTracking number for the shipment.
duplicate_charge_idThe Flex charge ID of the original, legitimate charge (for duplicate disputes).
duplicate_charge_explanationExplanation of why the charges are different (for duplicate disputes).
cancellation_policy_disclosureExplanation of how the cancellation policy was disclosed to the customer.
cancellation_rebuttalRebuttal to the customer’s cancellation claim.
refund_policy_disclosureExplanation of how the refund policy was disclosed.
refund_refusal_explanationExplanation of why a refund was not issued.
access_activity_logLog of customer access to the service (relevant for product_not_received).
uncategorized_textAny additional evidence or context.
File fields — use a Flex File ID (ffile_... from POST /v1/files) Files must be uploaded with purpose dispute_evidence before use. See Uploading Evidence Files.
FieldDescription
receiptReceipt or order confirmation.
customer_communicationEmail/chat correspondence with the customer.
customer_signatureSigned delivery confirmation or service agreement.
cancellation_policyCopy of your cancellation policy.
refund_policyCopy of your refund policy.
duplicate_charge_documentationDocumentation showing the original charge was legitimate.
service_documentationProof the service was delivered.
shipping_documentationShipping confirmation or carrier proof of delivery.
uncategorized_fileAny additional supporting document.
Example — save a draft (no submission)
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
Content-Type: application/json

{
  "dispute": {
    "evidence": {
      "customer_name": "Jane Doe",
      "customer_email_address": "jane@example.com",
      "product_description": "HSA-eligible blood pressure monitor, shipped 2026-01-15",
      "shipping_tracking_number": "1Z999AA10123456784",
      "shipping_carrier": "UPS",
      "shipping_date": "2026-01-15",
      "receipt": "ffile_01jx3qabc000000000000000001"
    },
    "submit": false
  }
}
Example — submit evidence
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8
Authorization: Bearer fsk_...
Content-Type: application/json

{
  "dispute": {
    "evidence": {
      "customer_name": "Jane Doe",
      "customer_email_address": "jane@example.com",
      "product_description": "HSA-eligible blood pressure monitor, shipped 2026-01-15",
      "shipping_tracking_number": "1Z999AA10123456784",
      "shipping_carrier": "UPS",
      "shipping_date": "2026-01-15",
      "receipt": "ffile_01jx3qabc000000000000000001",
      "shipping_documentation": "ffile_01jx3qabc000000000000000002"
    },
    "submit": true
  }
}
Response Returns the updated Dispute object with refreshed evidence_details.submission_count.
{
  "dispute": {
    "dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "status": "under_review",
    "evidence_details": {
      "due_by": "2026-03-01T23:59:59Z",
      "has_evidence": true,
      "past_due": false,
      "submission_count": 1
    },
    ...
  }
}
Errors
StatusWhen
404Dispute not found or belongs to a different partner.
422Dispute is not in needs_response / warning_needs_response status.
404A referenced file ID (ffile_...) does not exist.
422A referenced file was not uploaded with dispute_evidence purpose.

Close Dispute

POST /v1/disputes/:dispute_id/close
Accepts the dispute and concedes the funds. Use this when you do not intend to contest the dispute. This action is irreversible. The dispute must be in needs_response or warning_needs_response status. Path Parameters
ParameterDescription
dispute_idFlex dispute ID (fdsp_...).
Request Body No body required. Example
POST /v1/disputes/fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8/close
Authorization: Bearer fsk_...
Response Returns the updated Dispute object. status will be lost.
{
  "dispute": {
    "dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "status": "lost",
    ...
  }
}
Errors
StatusWhen
404Dispute not found or belongs to a different partner.
422Dispute is not in needs_response / warning_needs_response status.

Uploading Evidence Files

Before referencing a file in an evidence submission, upload it using the Files API with purpose dispute_evidence.
POST /v1/files
Authorization: Bearer fsk_...
Content-Type: multipart/form-data

file=@receipt.pdf
purpose=dispute_evidence
Response
{
  "file": {
    "file_id": "ffile_01jx3qabc000000000000000001",
    "filename": "receipt.pdf",
    "purpose": "dispute_evidence",
    "size": 48320,
    "created_at": "2026-02-10T14:00:00Z"
  }
}
Use the returned file_id (ffile_...) in evidence file fields. Attempting to use a file with a different purpose (e.g. identity_document) will return a 422 with a clear error message indicating which field is invalid and what purpose was found vs expected.

Webhook Events

Configure a webhook endpoint in your Flex dashboard to receive dispute lifecycle events.

charge.dispute.created

Fired when a dispute is opened against one of your charges.
{
  "event_id": "fevt_01jx3q8kt1b9a7n2c4d5e6f7g8",
  "event_type": "charge.dispute.created",
  "event_dt": 1770000000,
  "object": {
    "dispute_id": "fdsp_01jx3q8kt1b9a7n2c4d5e6f7g8",
    "payment_intent_id": "fpi_01jx3q7ab1c8d2e3f4g5h6j7k8",
    "charge_id": "fch_01jx3q6ab1c7d2e3f4g5h6j7k8",
    "amount": 4999,
    "currency": "usd",
    "status": "needs_response",
    "reason": "fraudulent",
    "evidence_details": {
      "due_by": "2026-03-01T23:59:59Z",
      "has_evidence": false,
      "past_due": false,
      "submission_count": 0
    }
  }
}
Recommended action: Alert your team and begin gathering evidence.

charge.dispute.updated

Fired when a dispute’s status, evidence, or deadline changes (including when you submit evidence via the API). Recommended action: Refresh your local dispute record.

charge.dispute.closed

Fired when a dispute is resolved — either won, lost, or warning_closed. Recommended action: Update your records; if lost, the funds have been debited.

charge.dispute.funds_withdrawn

Fired when funds are withdrawn from your balance to cover the disputed amount. A balance transaction is added to the dispute.

charge.dispute.funds_reinstated

Fired when a previously disputed amount is returned to your balance (dispute won). All webhook payloads include a flex-signature header for verification. See the Webhooks documentation for signature verification details.

Error Reference

All errors follow a standard format:
{
  "code": "validation_error",
  "detail": "Cannot submit evidence for dispute in 'under_review' status"
}
Or for field-level validation errors:
{
  "detail": [
    {
      "loc": ["evidence", "receipt"],
      "msg": "File ffile_01xxx in receipt has purpose identity_document, expected dispute_evidence",
      "type": "value_error"
    }
  ]
}
HTTP StatuscodeWhen
401unauthorizedMissing or invalid API key.
404not_foundDispute or file not found (or belongs to another partner).
422validation_errorInvalid status transition or file validation failure.
500server_errorInternal error — contact support with the request ID.

End-to-End Testing Guide

Prerequisites

  • Flex API key (test mode)
  • Test card numbers (see below)
  • A registered webhook endpoint to receive events

Step 1 — Trigger a dispute

Create a checkout session and complete payment using an auto-dispute test card:
Card NumberDispute ReasonNotes
4000 0000 0000 0259fraudulentAutomatically disputed after charge
4000 0000 0000 2685product_not_receivedAutomatically disputed after charge
Use 4111 1111 1111 1111 as a valid non-disputing card for comparison. After completing payment:
  • The charge is automatically disputed (test mode only)
  • Flex receives the charge.dispute.created event
  • Your webhook endpoint receives the charge.dispute.created event

Step 2 — Inspect the dispute

GET /v1/disputes
Authorization: Bearer fsk_...
Find the new dispute. Then fetch the full object:
GET /v1/disputes/fdsp_...
Authorization: Bearer fsk_...
Verify:
  • status: "needs_response"
  • evidence_details.due_by is a future timestamp
  • evidence_details.submission_count: 0
  • balance_transactions contains one debit entry

Step 3 — Submit evidence

POST /v1/disputes/fdsp_...
Authorization: Bearer fsk_...
Content-Type: application/json

{
  "dispute": {
    "evidence": {
      "customer_name": "Test Customer",
      "product_description": "HSA-eligible item, delivered",
      "uncategorized_text": "This charge is legitimate."
    },
    "submit": true
  }
}
Verify:
  • Response evidence_details.submission_count: 1
  • Response status changes to "under_review"

Step 4 — Close a second dispute

Create another charge with the auto-dispute card. Then:
POST /v1/disputes/fdsp_.../close
Authorization: Bearer fsk_...
Verify:
  • Response status: "lost"

Step 5 — Verify multi-tenant isolation

Using a different partner’s API key, attempt to access the dispute IDs from Steps 2–4. All should return 404.

Step 6 — Test error cases

Invalid status transition: Try POST /v1/disputes/fdsp_.../close on a dispute that is already lost or under_review. Expected: 422 with message "Cannot close dispute in '...' status". File with wrong purpose: Upload a file with purpose=identity_document, then reference it in an evidence field. Expected: 422 with message indicating which field has the wrong file purpose. Non-existent dispute:
GET /v1/disputes/fdsp_doesnotexist
Expected: 404.