Skip to content
DEMO PAYdocs
PTEN
Go to dashboard

Disputes and MED (Pix chargeback)

A Pix payment can be disputed after it settles. On Pix the channel for that is the MED (BACEN's Mecanismo Especial de Devolução): the payer asks their own bank to reverse a transaction they claim was fraud, a scam, or a mistake. When a MED is opened against one of your charges, Demo Pay detects it, holds the amount from your account while the case runs, and notifies you — so you don't discover the loss only at month-end.

This is different from a refund, which is a reversal you choose to issue. A MED is initiated by the payer, on their bank's side.

Today only the MagenPay provider delivers MED to Demo Pay automatically. Other providers will be wired into this same flow as they are integrated — from your side the mechanics are identical, whatever provider is behind it.

#Lifecycle at a glance

text
        charge paid
             │
   MED opened by the payer (provider webhook)
             │
             ▼
     status "disputed"  ── amount held from your account, refund blocked
             │
   ┌─────────┼───────────────────────────┐
   ▼         ▼                           ▼
  won       lost                     canceled
   │         │                           │
   ▼         ▼                           ▼
 back to   stays "disputed"          back to
  "paid"   (manual handling)          "paid"
 amount    amount NOT returned        amount
 returned                            returned

#What happens when a MED opens

  1. The charge becomes disputed. The public charge status (status vocabulary) moves from paid to disputed. A disputed charge cannot be refunded — the refund is blocked while the case is open, to prevent a double reversal (your refund + the bank-executed MED).
  2. Demo Pay holds the amount from your account. The charge amount is debited from your operational balance (it may go negative) and held as a reserve while the case runs. If you win, it comes back; if you lose, it was already reserved.
  3. You are notified. We send an email (med-opened) and the case appears in the Disputes panel of your dashboard, with the amount, the claimed reason, and the identity of whoever opened the MED (payer name/document, when the provider reports it), so you can reach out.
  4. No charge.* webhook is fired for the dispute transition. A MED is not a charge "failure" (it was paid), so we do not emit charge.failed to avoid misleading you. Detect the dispute via the email, the panel, or by re-reading the charge (GET /v1/charges/{id}status: "disputed").

#Case states

The dispute case has its own lifecycle, visible in the Disputes panel:

StateMeaningMoney effect
OPENMED just opened. Awaiting your decision (accept or appeal).Amount held.
ACCEPTEDYou accepted the loss without appealing.Amount stays held (loss taken).
APPEALEDYou appealed and attached your side/evidence; awaiting outcome.Amount stays held until the outcome.
REJECTEDAppeal did not prevail — MED upheld against you.Amount stays held (definitive loss).
CLOSEDClosed in your favor (you won, or the payer withdrew the claim).Amount returned to your account.

#Appeal and attach evidence

In the Disputes panel you can appeal an OPEN MED and attach evidence (invoice, delivery proof, conversations — anything that shows the charge was legitimate).

Important — what the appeal is, and isn't. Appealing is not an automatic submission to BACEN or to the claimant's bank. Today Demo Pay has no formal defense channel with MagenPay (the infraction API lives on the upstream Voluti infra, with no available credential). The appeal and evidence you submit are recorded internally, for the Demo Pay team to assess the case and handle it manually. The official outcome comes from the provider/BACEN and reaches us through the MED webhook.

#Outcomes

  • Won (disagreed) — the ruling was in your favor. The charge goes back to paid and the held amount is returned to your account. We send the resolved email (med-resolved, outcome: won).
  • Canceled (canceled) — the payer withdrew the claim. Treated as a win: the charge goes back to paid and the amount is returned.
  • Lost (agreed) — the reversal was actually executed; the money left (or will leave). Demo Pay never closes this case automatically — the charge stays disputed and the team handles it manually. The amount is not returned to you.

#Transaction metadata fields

From MED ingestion on, the transaction carries two informational blocks in metadata, surfaced on the charge's metadata object and in the admin transaction detail. They are informational and non-authoritative: they mirror what the provider reported, are read-only, and can change on each case event. Do not build money logic on top of them — the source of truth is the charge status and the Disputes panel.

#metadata.med — present on a disputed charge

FieldMeaning
providerInfractionIdThe MED case id at the provider (the fraudId). Not the E2E.
referenceIdThe end_to_end_id (E2E) of the original Pix being disputed.
statusCase state at the provider: created | delivered | closed | canceled.
resultRuling outcome, when closed: agreed (lost) | disagreed (won).
kindInfraction type: reversal | reversalChargeback.
methodClaimed reason: scam | unauthorized | coercion | invasion | other.
reasonTextual description of the case (from the provider).
analysisThe ruling's rationale, when present.
reportedByWho reported: debited (payer) | credited.
payerName / payerDocumentIdentity of whoever opened the MED, when the provider reports it.
operatorEmail / operatorPhoneCase operator contact, when reported.
ledgerTransactionIdTransaction id in the provider's ledger.
openedAt / updatedAt / closedAtCase timestamps at the provider (open / update / close).
lastEventAtWhen Demo Pay processed the latest event of this case.
amountMismatchtrue when the webhook amount diverged from the transaction amount. In that case, for safety, no status change is applied and the case goes to manual handling.

#metadata.failure — present on an expired / cancelled / failed charge

FieldMeaning
reasonTextual failure reason reported by the provider.
providerErrorCodeProvider code/status (e.g. expired, canceled) — what decided the final state.
ttlSecondsPix QR lifetime in seconds (pix.expires_at − creation), when the charge had an expiry. Absent when it didn't.
atWhen the failure was recorded (ISO 8601).

#Technical reference

  • The MagenPay pixInfraction webhook format and how Demo Pay correlates it: MagenPay — webhooks.
  • Voluntary refund (different from MED): see the refund question in Pix and the API reference.