Skip to main content
A dispute is a challenge against a payment your merchant has already received, where a third party asks for those funds to be returned. Rinne ingests the challenge from the payment provider, tracks it through a single lifecycle, and reports every step to your organization through the dispute.created and dispute.status-changed webhooks.
Rinne is never the claimant. It receives the provider’s report and relays it, so a dispute always originates outside Rinne—there is no way for your organization or Rinne to open one.

Where disputes come from

Disputes arrive from the provider that processed the original transaction. Each rail has its own vocabulary for the same idea, and Rinne absorbs that difference at the provider boundary: event names and payload fields describe Rinne’s lifecycle, not the provider’s. You integrate once, regardless of which rail a dispute came from. The type field names the rail:
Today disputes reach you from the PIX rail (PIX_INFRACTION). Card chargebacks will arrive through these same two events, so handling both types now needs no later change.
Disputes are pushed to you as webhooks; Rinne exposes no endpoint to open, update, or respond to one. To read a dispute’s current state, query it through the company or merchant dispute endpoints.

Lifecycle

A dispute tracks progress and verdict on two separate fields: status says how far the claim has got, and outcome says how it ended. status is an ordered progression and a dispute only ever moves forward. CLOSED is the single terminal status, so a finished dispute is exactly status == "CLOSED". outcome is null for the entire life of the dispute until it closes, and is always set once it has: A partially resolved claim is UPHELD with a smaller amount, not a separate outcome.
A dispute never regresses and never reopens. If you receive an event whose new_status is not ahead of the status you have stored, treat it as a duplicate and ignore it—old_status is always strictly earlier than new_status, so a stale or repeated provider delivery produces no event at all.

Result class

Outcomes group into two result classes by which side of the claim they favor. The class of a closed dispute never changes:

Merchant-favorable

DISMISSED and WITHDRAWN — the merchant keeps the funds.

Merchant-unfavorable

UPHELD — the funds go back to the payer.
Once a dispute is CLOSED, its outcome is final within its class, but it may be restated to the other outcome in the same class. The most common case: a claim closes DISMISSED because the reporting institution’s analysis disagreed, and the provider then sends a follow-up cancellation, which restates the same verdict as WITHDRAWN. Both mean the merchant keeps the funds. A restatement:
  • Overwrites outcome on the dispute.
  • Leaves status, amount, dispute_details, deadline_at, and concluded_at at their first-close values.
  • Does not emit another dispute.status-changed event, because the status did not move.
To observe a restated verdict, refetch the dispute through GET /v1/disputes/{id} for your own company, or GET /v1/merchants/disputes/{id} for your organization’s merchants. If your reconciliation only relies on events, a merchant-favorable dispute stays merchant-favorable and no action is required on your side. A terminal state whose outcome falls in the other result class (for example, a DISMISSED dispute followed by an UPHELD verdict) is treated as a contradiction, not a restatement. Rinne rejects it and no event fires; the dispute keeps its first-close outcome, and no further update is applied to it until Rinne resolves the conflict.

Disputes and the disputed transaction

Every dispute is linked to the transaction it challenges through transaction_id. Use that field to reconcile a dispute against the payment in your own system. The dispute lifecycle is tracked separately from the transaction lifecycle: a dispute.created or dispute.status-changed event reports the claim’s progress and is not a transaction status change. You receive dispute events independently of any transaction.status-changed event for the same payment. A single transaction can accumulate more than one dispute. Key your own records on dispute_id, not on transaction_id.

The dispute webhooks

Both events use the same envelope, Svix signatures, and retry behavior as every other Rinne event—see Webhooks. In the envelope, source is dispute-service and company_id is the merchant the claim is filed against. Both payloads carry an entity field holding the full dispute object as it stood when the event was emitted, so you never need a follow-up request to act on a dispute. The API reference lists every field it contains; a few are easy to misread:
  • amount is the amount at risk, in cents—an upper bound rather than an exact figure, currently derived from the disputed transaction. Rails that report an amount of their own will carry it here instead.
  • dispute_details is free text describing the claim, and its author is not recorded—it may be the payer in their own words, a risk analyst at the reporting institution, or a generated message. Display it and read it, but never parse it or branch your logic on its contents.
  • company_name (trading name, nullable) and company_full_name (legal name) identify the merchant the claim is filed against, alongside company_id. They match the pair the transaction response exposes, so you can show the merchant from the event without a second lookup. Both are optional on the entity snapshot: an event can carry neither, so fall back to company_id when they are absent. Neither field is in the dispute schema of the API reference yet.

dispute.created

Emitted when Rinne records a dispute for the first time.
A dispute is usually created at OPEN. A dispute whose first notification to reach Rinne already describes a finished claim is created at CLOSED, with outcome already set—so handle a created event that is already closed rather than assuming a status change will follow.

dispute.status-changed

Emitted when the dispute moves forward: OPEN to UNDER_REVIEW, or either of those to CLOSED with a verdict in outcome.
Group the events belonging to one dispute by dispute_id. Unlike some Rinne events, correlation_id here is unique per event rather than shared across a dispute’s lifetime, so it identifies the delivery, not the claim.
An outcome restated within the same result class (for example, DISMISSED → WITHDRAWN) does not emit a second dispute.status-changed. The status is still CLOSED, so nothing moved. If your workflow needs the latest outcome value, read the dispute through the API rather than waiting for another event.

Next steps

Webhooks

Endpoint setup, Svix signature verification, and retries

Transactions

The payment lifecycle a dispute challenges

Ledger entries

How settled amounts are recorded against a merchant

API reference

Full dispute event schemas under Webhooks → Events