Orders

Disputes and Chargebacks

A disputed payment is money you booked that is now contested — a card chargeback, or a B2B buyer refusing a Net-N invoice. Throttle surfaces both through the same three fields on the payment, written either by your own call or by the processor.

A dispute is a flag, not a lifecycle object
If you are coming from a processor with a first-class Dispute resource, calibrate expectations: Throttle stores disputed, disputeReason, and disputeOpenedAt on the payment, plus disputeOutcome and disputeClosedAt once a card chargeback is decided. There is no evidence submission, no due date, and no dispute id on the payment. The fields exist so a contested payment stops looking like clean revenue in your reporting and your AR views — contest the chargeback in your processor's dashboard.

Two ways a payment gets flagged

  1. You flag it. POST /api/v1/payments/{id}/dispute — mainly for Net-N receivables, where the dispute arrives as an email from a buyer rather than a processor event.
  2. The processor flags it. Inbound dispute.* and chargeback.* webhooks are matched to the payment by processor transaction id and write the same three fields. No configuration needed.

Because both paths converge on the same columns, a consumer of payment.disputed does not need to care which one fired.

Flagging a payment

Scope payment_disputes:write. reason is required, 1–500 characters. disputeOpenedAt is stamped server-side.

Flag a disputed invoice
curl -X POST https://api.usethrottle.dev/api/v1/payments/pay_88ce.../dispute \
  -H "X-API-Key: $THROTTLE_SECRET_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "reason": "Buyer claims the invoice was already settled by wire" }'
200 response
{
  "data": {
    "id": "pay_88ce...",
    "disputed": true,
    "disputeReason": "Buyer claims the invoice was already settled by wire",
    "disputeOpenedAt": "2026-08-09T14:41:02.000Z"
  }
}

Flagging an already-flagged payment is a 409 already_disputed rather than a silent overwrite, so you cannot lose the original reason by retrying.

Clearing a dispute

Clear the flag
curl -X POST https://api.usethrottle.dev/api/v1/payments/pay_88ce.../dispute/clear \
  -H "X-API-Key: $THROTTLE_SECRET_KEY"

Clearing sets disputed: false but deliberately leaves disputeReason and disputeOpenedAt in place, so the history of the dispute survives on the payment. It takes no body, and clearing a payment that was never disputed succeeds without emitting an event.

How processor events are classified

Inbound dispute events are classified from the event suffix and the target's status:

Processor signalOutcomeEffect
Status won or reversedClearSets disputed: false and disputeOutcome: "won", emits payment.dispute_cleared
Status lostLostSets disputed: false and disputeOutcome: "lost", emits payment.dispute_lost once; the order reads charged_back
Anything elseOpenSets disputed: true and clears any earlier outcome, emits payment.disputed once
Open is disputed; decided is disputeOutcome
disputed: true means a dispute is open right now — build your worklist from it. Once the processor decides a card chargeback, disputed goes back to false and disputeOutcome records won or lost, with disputeClosedAt. A lost chargeback rolls the order's paymentStatus up to charged_back. Net-N invoice disputes you clear by hand don't set an outcome.

A resolution event for a payment that is not currently flagged is a no-op, and redeliveries of an open event will not re-fire payment.disputed — it is emitted only on the first transition into the disputed state.

Webhooks

payment.disputed
{
  "type": "payment.disputed",
  "data": {
    "paymentId": "pay_88ce...",
    "reason": "fraudulent",
    "openedAt": "2026-08-09T14:41:02.000Z"
  }
}

payment.dispute_cleared carries only paymentId. payment.dispute_lost carries paymentId, reason, amount (minor units), currency, and closedAt, and fires once per loss. Subscribing to any of the three requires payment_disputes:read. Both are worth routing to a human — a chargeback is one of the few events where nobody finds out until a payout is short. See Webhooks.

Error codes

CodeHTTPMeaning
already_disputed409The payment is already flagged. Clear it first.
invalid_status409The payment is voided or refunded; there is nothing to dispute.
not_found404No payment with that id in this workspace environment.