> ## Documentation Index
> Fetch the complete documentation index at: https://paysure.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Webhooks

> Como a Paysure notifica seu sistema sobre eventos

## Como funciona

Toda transação eventualmente envia um **POST** para a `postbackUrl` que você forneceu na criação. O envelope é sempre tipado:

* **Cash-in** (cobrança recebida) → `{ "cashin": { ... } }`
* **Cash-out** (pagamento enviado) → `{ "cashout": { ... } }`

## Cash-in (recebimento confirmado)

```json theme={null}
{
  "cashin": {
    "reference_code": "PSR-9c8f7a1e-...",
    "external_reference": "pedido-001",
    "value_cents": 5000,
    "status": "paid",
    "payer_name": "João da Silva",
    "payer_document": "12345678900",
    "payment_date": "2026-05-13T10:13:01-03:00",
    "registration_date": "2026-05-13T10:12:15-03:00",
    "end_to_end": "E18236120202605131013...",
    "content": "00020126580014br.gov.bcb.pix..."
  }
}
```

### Status possíveis

| Status     | Significado                                                                            |
| ---------- | -------------------------------------------------------------------------------------- |
| `paid`     | Cliente pagou. Saldo da conta é creditado.                                             |
| `refunded` | Cliente fez devolução (BACEN) **ou** transação entrou em revisão MED. Saldo estornado. |
| `expired`  | QR Code expirou sem pagamento (após 24h, status terminal).                             |

### MED / Bloqueio por infração BACEN

Quando uma transação `paid` entra em revisão MED (chargeback), você recebe um webhook **adicional** com `status=refunded` e um campo extra `event` indicando o motivo:

```json theme={null}
{
  "cashin": {
    "reference_code": "PSR-9c8f7a1e-...",
    "external_reference": "pedido-001",
    "value_cents": 10000,
    "status": "refunded",
    "event": "med_block",
    "reason": "Transação em revisão MED — saldo bloqueado",
    "occurred_at": "2026-06-02T15:36:40-03:00",
    "end_to_end": "E18236120202606021536...",
    "payer_name": "João da Silva",
    "payer_document": "12345678900",
    "payment_date": "2026-06-01T02:04:46-03:00"
  }
}
```

* `value_cents` reflete o valor **bruto** da transação (não o líquido creditado) — é o que o BACEN tira da Paysure.
* A devolução é **irreversível pelo seu sistema**: bloqueie/estorne o saldo do cliente final imediatamente.
* Atualizações posteriores da disputa (vitória, derrota, cancelamento) **não** disparam novo webhook — são tratadas internamente.

<Note>
  Webhooks de MED só são enviados pra contas marcadas como **sub-gateway** (PSPs que repassam transações pra clientes finais). Se você opera direto com pagadores e não gerencia clientes finais, ignore essa seção. Pra habilitar como sub-gateway, fale com [suporte@paysurebr.com](mailto:suporte@paysurebr.com).
</Note>

## Cash-out (pagamento enviado)

```json theme={null}
{
  "cashout": {
    "reference_code": "PSR-1234abcd-...",
    "external_reference": "withdraw-001",
    "status": "paid",
    "end_to_end": "E29477089202605131013...",
    "occurred_at": "2026-05-13T10:13:01-03:00"
  }
}
```

### Status possíveis

| Status     | Significado                                                                                     |
| ---------- | ----------------------------------------------------------------------------------------------- |
| `paid`     | Pagamento concluído pelo banco do destinatário.                                                 |
| `refunded` | Não foi possível concluir. **Saldo é estornado automaticamente** — você não precisa fazer nada. |

## Boas práticas

<AccordionGroup>
  <Accordion title="Responda HTTP 200 sempre">
    Qualquer status diferente de 200 faz a Paysure retentar. Mesmo se você não reconhece o evento, devolva 200 com `{"ok": true}`.
  </Accordion>

  <Accordion title="Use o reference_code como idempotency key">
    Webhooks podem ser entregues mais de uma vez (em caso de retry após timeout). Use `cashin.reference_code` ou `cashout.reference_code` pra deduplicar do seu lado.

    ```javascript theme={null}
    if (await db.webhookAlreadyProcessed(payload.cashin.reference_code)) {
      return res.status(200).json({ ok: true });
    }
    ```
  </Accordion>

  <Accordion title="Valide a origem">
    A Paysure envia webhooks de IPs Cloudflare. Se quiser camada extra de segurança, valide o IP de origem ou implemente uma assinatura HMAC compartilhada (em breve).
  </Accordion>

  <Accordion title="Timeout">
    Respondemos em até 10 segundos antes de considerar timeout e retentar. Mantenha o handler rápido — processe em background se precisar de operações pesadas.
  </Accordion>
</AccordionGroup>

## Política de retry

Em caso de falha (HTTP != 200 ou timeout), retentamos com backoff:

* 1ª tentativa: imediata
* 2ª tentativa: \~5 minutos
* 3ª tentativa: \~30 minutos

Após 3 falhas, o webhook é marcado como `client_error` e fica disponível pra retry manual via painel.
