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
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
- The charge becomes
disputed. The public charge status (status vocabulary) moves frompaidtodisputed. Adisputedcharge cannot be refunded — the refund is blocked while the case is open, to prevent a double reversal (your refund + the bank-executed MED). - 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.
- 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. - No
charge.*webhook is fired for the dispute transition. A MED is not a charge "failure" (it was paid), so we do not emitcharge.failedto 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:
| State | Meaning | Money effect |
|---|---|---|
OPEN | MED just opened. Awaiting your decision (accept or appeal). | Amount held. |
ACCEPTED | You accepted the loss without appealing. | Amount stays held (loss taken). |
APPEALED | You appealed and attached your side/evidence; awaiting outcome. | Amount stays held until the outcome. |
REJECTED | Appeal did not prevail — MED upheld against you. | Amount stays held (definitive loss). |
CLOSED | Closed 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 topaidand 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 topaidand 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 staysdisputedand 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
| Field | Meaning |
|---|---|
providerInfractionId | The MED case id at the provider (the fraudId). Not the E2E. |
referenceId | The end_to_end_id (E2E) of the original Pix being disputed. |
status | Case state at the provider: created | delivered | closed | canceled. |
result | Ruling outcome, when closed: agreed (lost) | disagreed (won). |
kind | Infraction type: reversal | reversalChargeback. |
method | Claimed reason: scam | unauthorized | coercion | invasion | other. |
reason | Textual description of the case (from the provider). |
analysis | The ruling's rationale, when present. |
reportedBy | Who reported: debited (payer) | credited. |
payerName / payerDocument | Identity of whoever opened the MED, when the provider reports it. |
operatorEmail / operatorPhone | Case operator contact, when reported. |
ledgerTransactionId | Transaction id in the provider's ledger. |
openedAt / updatedAt / closedAt | Case timestamps at the provider (open / update / close). |
lastEventAt | When Demo Pay processed the latest event of this case. |
amountMismatch | true 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
| Field | Meaning |
|---|---|
reason | Textual failure reason reported by the provider. |
providerErrorCode | Provider code/status (e.g. expired, canceled) — what decided the final state. |
ttlSeconds | Pix QR lifetime in seconds (pix.expires_at − creation), when the charge had an expiry. Absent when it didn't. |
at | When the failure was recorded (ISO 8601). |
#Technical reference
- The MagenPay
pixInfractionwebhook format and how Demo Pay correlates it: MagenPay — webhooks. - Voluntary refund (different from MED): see the refund question in Pix and the API reference.