> ## Documentation Index
> Fetch the complete documentation index at: https://docs.safefypay.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# MED webhooks

> Events sent when a MED (dispute) case is opened and when it ends

When a PIX payment receives a MED (Special Return Mechanism) or when the case ends, Safefy sends a `dispute.*` event to
your organization's webhooks that subscribe to it. The `type` field tells which stage the case is in, and the `data`
object carries a snapshot of the case at that moment.

## How to receive them

MED events are delivered through the webhooks registered in the dashboard, not through the payment `callbackUrl`:

1. In the Safefy dashboard, open **Webhooks** and click **New webhook**.
2. Enter the URL that will receive the events.
3. In the **Dispute** group, select the `dispute.*` events you want.
4. Save and store the **secret** shown on screen. It signs this webhook's events and is shown only once.

## Events

| `type` | When it is sent | `data.status` | Balance effect |
| - | - | - | - |
| `dispute.opened` | A MED was opened for one of your organization's payments | `Open` | The disputed amount leaves available and is held |
| `dispute.won` | The MED was resolved in your favor: you keep the amount | `Won` | The hold goes back to the available balance |
| `dispute.lost` | The MED was resolved against you: the amount went back to the payer | `Lost` | The held amount is debited for good |
| `dispute.cancelled` | The case was closed without a decision (cancelled or deadline expired) | `Cancelled`, `Expired` | The hold goes back to the available balance |

> [!NOTE]
>
> A MED may carry a fee, according to your organization's terms. The amount charged on the case is in `data.medFee`.

A case that already arrives with an outcome (for example, a MED opened and lost in the same processor notice) produces
both events, in this order: `dispute.opened` and then the outcome event.

## Headers

| Header | Description |
| - | - |
| `X-Safefy-Event` | Event type (`dispute.opened`, `dispute.won`, `dispute.lost`, `dispute.cancelled`) |
| `X-Safefy-Delivery` | Unique delivery ID |
| `X-Safefy-Attempt` | Attempt number (`1`, `2`, `3`...) |
| `X-Safefy-Timestamp` | Send time, in Unix seconds |
| `X-Safefy-Signature-V2` | Signature `t={timestamp},v1={signature}` with the registered webhook's secret |

Validate the signature as described in [Webhooks → Signature](https://docs.safefypay.com.br/en/webhooks.md#signature), using the registered webhook's
secret (not the payment and cashout callback secret).

## Payload

Amounts in cents. Dates in UTC (ISO 8601).

```json dispute.opened theme={null}
{
  "id": "0193f0a2-6c1e-7b8a-9d41-2f6a1c0e9b77",
  "type": "dispute.opened",
  "createdAt": "2026-10-29T14:03:12Z",
  "data": {
    "id": "0193f0a2-6a90-7c55-8e2b-41d7a9c3f001",
    "status": "Open",
    "reason": "Scam",
    "reasonDescription": "Payer says they do not recognize the purchase.",
    "amount": 4000,
    "currency": "BRL",
    "environment": "Production",
    "openedAt": "2026-10-29T14:03:11Z",
    "responseDeadline": "2026-11-05T23:59:59Z",
    "closedAt": null,
    "holdState": "Held",
    "holdAmount": 4000,
    "heldAmount": 4000,
    "medFee": 0,
    "payment": {
      "id": "8f8fce31-5c83-42c4-b90b-c29636b595e3",
      "externalId": "order-1234",
      "amount": 10000,
      "status": "Disputed",
      "endToEndId": "E00000000202610291400abcdef12345",
      "txId": "safefy8f8fce315c8342c4b90bc29636"
    }
  }
}
```

```json dispute.lost theme={null}
{
  "id": "0193f5d1-2b44-7f10-a3c9-77e0b1d2c3a4",
  "type": "dispute.lost",
  "createdAt": "2026-11-04T10:20:45Z",
  "data": {
    "id": "0193f0a2-6a90-7c55-8e2b-41d7a9c3f001",
    "status": "Lost",
    "reason": "Scam",
    "reasonDescription": "Payer says they do not recognize the purchase.",
    "amount": 4000,
    "currency": "BRL",
    "environment": "Production",
    "openedAt": "2026-10-29T14:03:11Z",
    "responseDeadline": "2026-11-05T23:59:59Z",
    "closedAt": "2026-11-04T10:20:44Z",
    "holdState": "Debited",
    "holdAmount": 4000,
    "heldAmount": 0,
    "medFee": 300,
    "payment": {
      "id": "8f8fce31-5c83-42c4-b90b-c29636b595e3",
      "externalId": "order-1234",
      "amount": 10000,
      "status": "Refunded",
      "endToEndId": "E00000000202610291400abcdef12345",
      "txId": "safefy8f8fce315c8342c4b90bc29636"
    }
  }
}
```

### Fields

| Field | Type | Description |
| - | - | - |
| `id` | string (uuid) | Event ID. Use it to drop redeliveries |
| `type` | string | Case stage (see the events table) |
| `createdAt` | string | When the event was generated |
| `data.id` | string (uuid) | Dispute ID. Same as [Get a dispute](https://docs.safefypay.com.br/en/api-reference/disputes/get.md) |
| `data.status` | string | `Open`, `Won`, `Lost`, `Cancelled` or `Expired` |
| `data.reason` | string | MED category (table below) |
| `data.reasonDescription` | string or null | Reason details |
| `data.amount` | integer | Disputed amount |
| `data.currency` | string | Currency (`BRL`) |
| `data.environment` | string | `Production` or `Sandbox` |
| `data.openedAt` | string | When the case was opened |
| `data.responseDeadline` | string or null | Deadline to submit your defense |
| `data.closedAt` | string or null | When the case was closed |
| `data.holdState` | string | Hold state: `None`, `Held`, `Released` or `Debited` |
| `data.holdAmount` | integer | Amount moved by the hold |
| `data.heldAmount` | integer | Amount still held right now (`0` once the hold is released or debited) |
| `data.medFee` | integer | MED fee charged on the case (`0` when none was charged) |
| `data.payment` | object or null | Disputed payment: `id`, `externalId`, `amount`, `status`, `endToEndId` and `txId` |

> [!NOTE]
>
> `holdAmount` can be lower than `amount` when part of the payment had already been refunded to the payer before the
> MED: Safefy only holds what is still with you.

### Categories (`data.reason`)

| Value | Description |
| - | - |
| `Fraud` | Fraud |
| `Scam` | Scam (social engineering) |
| `ProductNotReceived` | Product or service not received |
| `ProductNotAsDescribed` | Product or service not as described |
| `DuplicateCharge` | Duplicate charge |
| `UnrecognizedTransaction` | Transaction not recognized by the payer |
| `CoercionOrThreat` | Coercion or threat |
| `OperationalError` | Operational error |
| `Other` | Other reason |

## Delivery

* Reply with `2xx` to confirm. Without confirmation, Safefy retries: up to 3 attempts, or the number configured on the
  webhook.
* Every delivery (with the body sent and the response received) shows up in the dashboard, under **Webhooks**.

## Best practices

* **Idempotency:** store the event `id` and ignore events you already processed. Do not rely on arrival order:
  `data.status` is the source of truth for the case.
* **`dispute.opened`:** flag the sale as disputed and hold product or service delivery. Submit your defense and evidence
  before `data.responseDeadline` with [Submit a response](https://docs.safefypay.com.br/en/api-reference/disputes/respond.md) and
  [Add evidence](https://docs.safefypay.com.br/en/api-reference/disputes/add-evidence.md).
* **`dispute.won` and `dispute.cancelled`:** the sale returns to its normal flow. You keep the amount.
* **`dispute.lost`:** record the return to the payer and, if it makes sense, revoke access to the product.
* **`data.reason`:** use the category to automate internal rules, such as reviewing sales in recurring categories.
* To see the full case (submitted defense, evidence and timeline), call [Get a dispute](https://docs.safefypay.com.br/en/api-reference/disputes/get.md)
  with `data.id`.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.