# PagzPay API — Documentação

API de pagamentos para o mercado brasileiro: PIX (QR Code), cartão de crédito (Pagar.me),
transferências PIX, saldo em tempo real, split entre contas PagzPay e webhooks assinados.

**Base URL:** `https://api.pagzpay.com`

Acesse o [Dashboard](https://app.pagzpay.com) e gere suas credenciais em
**Configurações → API Keys** antes de começar.

## Sumário

- [Autenticação](#autenticação)
- [Valores monetários](#valores-monetários)
- [PIX IN](#pix-in)
  - [Criar QR Code](#criar-qr-code)
  - [Consultar QR Code](#consultar-qr-code)
- [Cartão de crédito](#cartão-de-crédito)
  - [Criar cobrança](#criar-cobrança-cartão)
  - [Checkout hospedado com cartão](#checkout-hospedado-com-cartão)
- [Dados do produto (compliance)](#dados-do-produto-compliance)
- [Rastreamento (UTMs / Utmify)](#rastreamento-utms--utmify)
- [PIX OUT](#pix-out)
  - [Enviar transferência](#enviar-transferência)
  - [Consultar transferência](#consultar-transferência)
- [Conta](#conta)
  - [Consultar saldo](#consultar-saldo)
- [Webhooks](#webhooks)
- [Infrações (MED)](#infrações-med)
- [Split de pagamentos](#split-de-pagamentos)
- [Idempotência](#idempotência)
- [Erros](#erros)
- [Referência rápida de endpoints](#referência-rápida-de-endpoints)

---

## Autenticação

Gera um token JWT para autenticar as demais requisições. Concatene `API Key:API Secret`
(gerados no Dashboard em **Configurações → API Keys**), codifique em Base64 e envie como
Basic Auth. Esta rota não exige body — apenas o header `Authorization`.
Body vazio com `Content-Type: application/json` (axios) ou
`application/x-www-form-urlencoded` (PHP `CURLOPT_POST`) é aceito.

```
POST /api/auth
```

| Header | Valor |
|---|---|
| `Authorization` | `Basic base64(API Key:API Secret)` |

```bash
credentials=$(echo -n "SUA_API_KEY:SEU_API_SECRET" | base64)

curl -X POST 'https://api.pagzpay.com/api/auth' \
  -H "Authorization: Basic $credentials"
```

**Resposta 200:**

```json
{
  "success": true,
  "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
  "tokenType": "Bearer",
  "expiresIn": 60000
}
```

Use o `token` retornado como `Authorization: Bearer {token}` em todas as chamadas seguintes.

## Valores monetários

Todos os valores enviados e recebidos pela API são **centavos, em inteiros** — nunca reais
fracionados. A única exceção é o endpoint de saldo, que retorna reais como string com duas casas
decimais.

| Valor em reais | Enviar na API |
|---|---|
| R$ 1,00 | `100` |
| R$ 10,50 | `1050` |
| R$ 150,00 | `15000` |

Nunca envie valores com ponto ou vírgula decimal — `10.50` é interpretado de forma incorreta.

## PIX IN

### Criar QR Code

Cria um QR Code PIX para receber um pagamento imediato. Retorna o código EMV (copia e cola) e a
imagem do QR Code em Base64. Informe `postbackUrl` para receber uma notificação automática quando
o pagamento for confirmado, sem precisar fazer polling.

```
POST /api/v1/pix/in/qrcode
```

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `amountInCents` | integer | Sim | Valor da cobrança em centavos (mínimo R$ 1,00). |
| `customer` | object | Sim | Dados do pagador — `documentType` e `document` obrigatórios. |
| `description` | string | Não | Até 140 caracteres. Aparece no extrato do pagador. |
| `postbackUrl` | string | Não | URL que recebe a notificação de confirmação do pagamento. Pode incluir UTMs na query (ver [Rastreamento](#rastreamento-utms--utmify)). |
| `trackingParameters` | object | Não | Parâmetros de campanha (UTMs, click IDs) para atribuição na Utmify e relatórios. Ver [Rastreamento](#rastreamento-utms--utmify). |
| `items` | array | Não | Itens do pedido: `title` (nome real do produto), `imageUrl` (foto do produto), `tangible`, `quantity`, `amountInCents`. |
| `salesSiteUrl` | string | Não | URL do site onde o produto é vendido. Ver [Dados do produto](#dados-do-produto-compliance). |
| `checkoutUrl` | string | Não | URL da página de checkout onde o pagamento foi feito. Ver [Dados do produto](#dados-do-produto-compliance). |
| `supportContact` | object | Não | Contato de suporte do vendedor: `email`, `phone`, `whatsapp` e/ou `url`. Ver [Dados do produto](#dados-do-produto-compliance). |
| `splits` | array | Não | Distribui o valor líquido para outras empresas PagzPay. Ver [Split de pagamentos](#split-de-pagamentos). |

Objeto `customer`: `documentType` (`cpf`/`cnpj`, obrigatório), `document` (obrigatório), `name`,
`email`, `phone`, `billingAddress`.

```bash
curl -X POST 'https://api.pagzpay.com/api/v1/pix/in/qrcode' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
  -H 'Content-Type: application/json' \
  -d '{
    "amountInCents": 5000,
    "description": "Pagamento de serviços",
    "postbackUrl": "https://exemplo.com.br/webhook",
    "trackingParameters": {
      "src": "facebook",
      "sck": "sck-exemplo",
      "utm_source": "meta",
      "utm_campaign": "campanha-exemplo",
      "utm_medium": "cpc",
      "utm_content": "criativo-1",
      "utm_term": "tenis",
      "fbclid": "fbclid-exemplo",
      "gclid": "gclid-exemplo",
      "ttclid": "ttclid-exemplo",
      "xcod": "xcod-exemplo"
    },
    "customer": {
      "name": "João Silva",
      "email": "joao@exemplo.com",
      "documentType": "cpf",
      "document": "11122233344",
      "phone": "(11) 91234-5678"
    }
  }'
```

**Resposta 200:**

```json
{
  "success": true,
  "message": "Transação criada com sucesso",
  "data": {
    "id": "trx_1a2b3c4d5e6f7g8h9i0j",
    "pix": {
      "emv": "00020126580014br.gov.bcb.pix0136...",
      "qrCode": "iVBORw0KGgo..."
    },
    "status": "pending",
    "fees": 0
  }
}
```

### Consultar QR Code

```
GET /api/v1/pix/in/qrcode/:transactionId
```

Status possíveis: `pending`, `paid`, `canceled`, `refunded`.

```bash
curl -X GET 'https://api.pagzpay.com/api/v1/pix/in/qrcode/trx_1a2b3c4d5e6f7g8h9i0j' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI'
```

## Cartão de crédito

Disponível com adquirente **Pagar.me** configurada para a empresa (`cardAdquirenceId` no admin).
Exige **`customer.billingAddress`** (rua, número, bairro, cidade, UF, CEP).

### Tokenização (Pagar.me — recomendado)

A PagzPay **não** expõe endpoint de tokenização. No fluxo API, gere o token **no cliente** contra a Pagar.me:

```
POST https://api.pagar.me/core/v5/tokens?appId={PUBLIC_KEY}
```

- **PUBLIC_KEY**: chave pública Pagar.me (`pk_test_…` / `pk_…`) da conta ligada à adquirente de cartão.
- No **checkout hospedado**, use `paymentCapabilities.card.publicKey` retornado na carga pública do link.

Body (exemplo):

```json
{
  "type": "card",
  "card": {
    "number": "4111111111111111",
    "holder_name": "JOAO SILVA",
    "exp_month": "12",
    "exp_year": "28",
    "cvv": "123"
  }
}
```

Resposta: `{ "id": "token_…" }` → envie em `card.token` no `POST /api/v1/card`.

### Criar cobrança (cartão)

```
POST /api/v1/card
```

Envie os dados do cartão **ou** um **token** gerado no navegador (recomendado — PCI). No checkout
hospedado PagzPay, a tokenização é feita automaticamente com a chave pública Pagar.me.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `amountInCents` | integer | Sim | Valor em centavos. |
| `customer` | object | Sim | Mesmo formato do PIX + **`billingAddress` obrigatório**. |
| `card` | object | Sim | `holderName`, `installments` (1–12). **Token** (`token`) ou PAN (`number`, `expirationDate` MM/AA, `cvv`). |
| `postbackUrl` | string | Não | Webhook de confirmação/atualização de status. |
| `trackingParameters` | object | Não | UTMs (Utmify). |
| `items`, `salesSiteUrl`, `checkoutUrl`, `supportContact` | — | Não | Dados do produto para compliance — mesmo formato do PIX. Ver [Dados do produto](#dados-do-produto-compliance). |

Status inicial comum: `paid` (aprovado na hora), `pending` (análise) ou `refused`.

### Consultar cobrança (cartão)

Use o `id` retornado na criação:

```
GET /api/v1/transactions/{transactionId}
GET /api/v1/transactions/{transactionId}/status
```

O primeiro retorna a transação completa (`paymentMethod`, `Card.brand`, `Card.last4`, taxas, etc.).
O segundo retorna apenas `{ "success": true, "status": "paid" }` — ideal para polling.

```bash
curl -X GET 'https://api.pagzpay.com/api/v1/transactions/trx_abc123' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI'
```

```bash
curl -X POST 'https://api.pagzpay.com/api/v1/card' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
  -H 'Content-Type: application/json' \
  -d '{
    "amountInCents": 5000,
    "postbackUrl": "https://exemplo.com.br/webhook",
    "trackingParameters": {
      "src": "facebook",
      "sck": "sck-exemplo",
      "utm_source": "meta",
      "utm_campaign": "campanha-exemplo",
      "utm_medium": "cpc",
      "utm_content": "criativo-1",
      "utm_term": "tenis",
      "fbclid": "fbclid-exemplo",
      "gclid": "gclid-exemplo",
      "ttclid": "ttclid-exemplo",
      "xcod": "xcod-exemplo"
    },
    "customer": {
      "name": "João Silva",
      "email": "joao@exemplo.com",
      "documentType": "cpf",
      "document": "11122233344",
      "billingAddress": {
        "street": "Av. Paulista",
        "number": "1000",
        "neighborhood": "Bela Vista",
        "city": "São Paulo",
        "state": "SP",
        "zipCode": "01310100"
      }
    },
    "card": {
      "token": "tok_xxxxxxxx",
      "holderName": "JOAO SILVA",
      "installments": 1
    }
  }'
```

### Checkout hospedado com cartão

1. Admin: adquirente de **cartão** (global ou por empresa) apontando para Pagar.me.
2. No builder do checkout, habilite **Cartão de crédito** em métodos aceitos.
3. A página pública expõe `paymentCapabilities.card` (inclui `publicKey` para tokenização).
4. Pagamento: `POST /checkout/{token}/pay/card` (sem auth do comprador; token do cartão no body).

Consulte status da transação com o mesmo endpoint usado no PIX do checkout:
`GET /checkout/{token}/status/{transactionId}`.

## Dados do produto (compliance)

A adquirente pede informações sobre o que está sendo vendido para análise de compliance. Nas
cobranças via API (`POST /api/v1/pix/in/qrcode` e `POST /api/v1/card`) **todos esses campos são
opcionais** — se não forem enviados, a cobrança funciona normalmente. Recomendamos enviá-los
sempre que possível: o PagzPay repassa os dados à adquirente junto com o pedido.

| Campo | Tipo | Descrição |
|---|---|---|
| `items[].title` | string | Nome real do produto (vai no lugar de uma descrição genérica). |
| `items[].imageUrl` | string (URL) | URL pública da foto real do produto. |
| `salesSiteUrl` | string (URL) | URL do site onde o produto é vendido. |
| `checkoutUrl` | string (URL) | URL da página de checkout onde o pagamento foi feito. |
| `supportContact.email` | string | E-mail de suporte do vendedor. |
| `supportContact.phone` | string | Telefone de suporte com DDD. |
| `supportContact.whatsapp` | string | WhatsApp de suporte com DDD. |
| `supportContact.url` | string (URL) | Site ou página de ajuda. |

Em `supportContact`, envie os canais que preferir — todos são opcionais. URLs precisam ser
completas (com `https://`) e a foto precisa estar acessível publicamente.

```bash
curl -X POST 'https://api.pagzpay.com/api/v1/pix/in/qrcode' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
  -H 'Content-Type: application/json' \
  -d '{
    "amountInCents": 9790,
    "description": "Pedido #4321",
    "customer": {
      "name": "Ana Lima",
      "email": "ana@exemplo.com",
      "documentType": "cpf",
      "document": "12345678909"
    },
    "items": [
      {
        "title": "Tênis Corrida Pro",
        "imageUrl": "https://minhaloja.com.br/img/tenis-corrida-pro.jpg",
        "tangible": true,
        "quantity": 1,
        "amountInCents": 9790
      }
    ],
    "salesSiteUrl": "https://minhaloja.com.br",
    "checkoutUrl": "https://minhaloja.com.br/checkout/4321",
    "supportContact": {
      "email": "suporte@minhaloja.com.br",
      "whatsapp": "(11) 99999-0000"
    }
  }'
```

**Checkout hospedado PagzPay:** no checkout builder, nome do produto, foto do produto e ao menos
um contato de suporte são **obrigatórios**. O contato aparece no rodapé do checkout, e esses
dados, junto com a URL do checkout e a página de origem do comprador, são enviados
automaticamente — não é preciso fazer nada na integração.

## Rastreamento (UTMs / Utmify)

Existem **dois fluxos** para receber PIX no PagzPay. Em ambos, a venda pode ser enviada à Utmify
quando o pagamento confirma — desde que a integração esteja ativa e os parâmetros de campanha
tenham sido **gravados na transação**.

| | **Checkout hospedado** | **API PIX** |
|---|---|---|
| Quem paga | Página PagzPay (link de checkout) | Sua página / funil + QR gerado por API |
| Como passar UTMs | Na **URL do checkout** (query string) | No body: **`trackingParameters`** |
| Captura automática | Sim (URL, cookies, script Utmify na página) | Não — você envia no JSON |
| Endpoint | Página pública (sem auth do pagador) | `POST /api/v1/pix/in/qrcode` ou `/cob` |

### Passo 0 — Configurar Utmify (igual nos dois fluxos)

1. Acesse o [Dashboard PagzPay](https://app.pagzpay.com) → **Integrações → Utmify**.
2. Cole o **token** da Utmify (Credenciais de API) e deixe a integração **ativa**.
3. Opcional: inclua **e-mail do cliente** no pagamento — ajuda a Utmify a casar venda com pixel.

---

### Fluxo 1 — Checkout hospedado

Use quando o cliente paga em um **link de checkout** criado no PagzPay (ex. `checkout.pagzpay.com/{slug}/{token}` ou domínio customizado).

**Como configurar**

1. Crie o link em **Checkout** no dashboard e copie a URL completa.
2. No funil (Meta, landing, Skale, etc.), o redirect para o PagzPay deve **manter ou acrescentar** os parâmetros UTM na URL do **checkout**, não só na landing:
   ```
   https://checkout.pagzpay.com/seu-slug/abc123xyz?utm_source=meta&utm_campaign=natal&utm_medium=cpc
   ```
3. O cliente abre esse link, preenche e paga. A página carrega o script Utmify, lê query/cookies e envia os dados no momento do PIX.
4. Ao confirmar o pagamento, o PagzPay notifica a Utmify com os `trackingParameters` gravados.

**Importante:** UTMs só na landing (ex. `seusite.com/pagamento.html?utm_...`) **não** aparecem
sozinhas no checkout em outro domínio. Repasse as UTMs no link **de saída** para o PagzPay.

**Teste rápido:** abra o link do checkout com `?utm_source=teste&utm_campaign=doc` na URL, pague
e confira na Utmify.

---

### Fluxo 2 — API PIX

Use quando **sua aplicação** chama a API para gerar o QR Code (PHP, Node, Skale, etc.).

**Como configurar**

1. Autentique em `POST /api/auth` e use o Bearer token.
2. Na criação da cobrança (`POST /api/v1/pix/in/qrcode`), inclua o objeto **`trackingParameters`** no JSON (nome exato — não use `tracking` ou `utms`).
3. Obtenha os valores:
   - **Front-end:** leia `window.location.search` e monte o objeto (ou envie ao seu backend).
   - **Back-end:** leia UTMs da sessão/query do funil no momento em que o usuário entrou e inclua no body da API.
4. Alternativa: coloque UTMs na query da **`postbackUrl`** (o PagzPay faz merge; `trackingParameters` no body tem prioridade).

**Cliente:** use `customer.documentType` + `customer.document` (não envie `cpf` como campo separado).

**Exemplo**

```bash
curl -X POST 'https://api.pagzpay.com/api/v1/pix/in/qrcode' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
  -H 'Content-Type: application/json' \
  -d '{
    "amountInCents": 3590,
    "description": "Produto digital",
    "postbackUrl": "https://seusite.com/api/webhook.php",
    "customer": {
      "name": "Cliente",
      "email": "cliente@exemplo.com",
      "documentType": "cpf",
      "document": "11122233344"
    },
    "trackingParameters": {
      "src": "facebook",
      "sck": "sck-exemplo",
      "utm_source": "meta",
      "utm_campaign": "campanha-exemplo",
      "utm_medium": "cpc",
      "utm_content": "criativo-1",
      "utm_term": "tenis",
      "fbclid": "fbclid-exemplo",
      "gclid": "gclid-exemplo",
      "ttclid": "ttclid-exemplo",
      "xcod": "xcod-exemplo"
    }
  }'
```

---

### Campos de `trackingParameters`

| Campo | Descrição |
|---|---|
| `utm_source`, `utm_campaign`, `utm_medium`, `utm_content`, `utm_term` | UTMs padrão |
| `src`, `sck`, `xcod` | Campos Utmify |
| `fbclid`, `gclid`, `ttclid` | Click IDs |

Envie apenas campos preenchidos. Valores vazios não entram na atribuição.

### Validar se está funcionando

1. Após pagar, abra a venda no dashboard PagzPay — a metadata deve conter `trackingParameters`.
2. **Integrações → Utmify → Reenviar** com o ID da transação para ver a resposta da API.
3. Na Utmify, a venda não deve aparecer como **“UTMs vazias”** se os dados foram gravados.

### Erros comuns

| Sintoma | Causa usual |
|---|---|
| UTMs vazias + **API** | Body sem `trackingParameters` e `postbackUrl` sem query UTM. |
| UTMs vazias + **checkout** | Funil abre checkout **sem** query UTM; corrija o redirect. |
| UTMs só na landing | Cookie da landing não vai para outro domínio — repasse na URL ou API. |
| Campo ignorado | Nome errado (`tracking`) ou `customer.cpf` em vez de `document`. |

## PIX OUT

### Enviar transferência

Realiza uma transferência PIX para qualquer chave PIX. **Irreversível** após processada — sempre
use o header `x-idempotency-key` (ver [Idempotência](#idempotência)).

```
POST /api/v1/pix/out/pixkey
```

Tipos de chave aceitos: CPF (`11122233344`), CNPJ (`11222333000181`), e-mail
(`destino@email.com`), telefone (`+5511999999999`), chave aleatória (UUID).

| Header | Obrig. | Descrição |
|---|---|---|
| `x-idempotency-key` | Sim | UUID único por operação — evita transferências duplicadas em retry. |

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `pixKey` | string | Sim | Chave PIX de destino. |
| `amount` | integer | Sim | Valor da transferência em centavos. |
| `currency` | string | Não | Moeda — apenas `BRL` (padrão). |
| `description` | string | Não | Até 140 caracteres. |
| `postbackUrl` | string | Não | URL para receber notificação de status da transferência. |

```bash
curl -X POST 'https://api.pagzpay.com/api/v1/pix/out/pixkey' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI' \
  -H 'Content-Type: application/json' \
  -H 'x-idempotency-key: 550e8400-e29b-41d4-a716-446655440000' \
  -d '{
    "pixKey": "fornecedor@exemplo.com",
    "amount": 10000,
    "currency": "BRL",
    "description": "Pagamento de fornecedor",
    "postbackUrl": "https://exemplo.com.br/webhook/pix-out"
  }'
```

**Resposta 200:**

```json
{
  "success": true,
  "message": "Transferência criada com sucesso",
  "data": {
    "id": "transfer_abc123def456",
    "status": "pending",
    "amount": 10000,
    "netAmount": 10000,
    "fees": 0,
    "pixKey": "fornecedor@exemplo.com"
  }
}
```

### Consultar transferência

```
GET /api/v1/pix/out/pixkey/:transferId
```

Status possíveis: `queued`, `processing`, `pending`, `pending_analysis`, `banking_processing`,
`banking_refused`, `banking_error`, `manual_analysis`, `completed`, `canceled`, `refused`, `error`.

```bash
curl -X GET 'https://api.pagzpay.com/api/v1/pix/out/pixkey/transfer_abc123def456' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI'
```

## Conta

### Consultar saldo

Retorna os saldos das carteiras da empresa autenticada. Os valores vêm em **reais**, como string
com 2 casas decimais — a única exceção à regra de centavos da API.

```
GET /api/v1/balance
```

| Campo | Descrição |
|---|---|
| `pix.balance` | Saldo livre, disponível para uso e transferências. |
| `pixBlocked.balance` | Saldo bloqueado por cautelar ou MED (indisponível). |
| `reserve.balance` | Valor em reserva, aguardando liquidação. |

```bash
curl -X GET 'https://api.pagzpay.com/api/v1/balance' \
  -H 'Authorization: Bearer SEU_TOKEN_AQUI'
```

## Webhooks

Configure uma URL e escolha quais eventos deseja monitorar. O PagzPay envia um `POST` com o
payload do evento. Além dos webhooks cadastrados, quando uma cobrança/transferência é criada com
`postbackUrl`, o mesmo callback é enviado para essa URL.

| Fluxo | Evento | Quando é enviado |
|---|---|---|
| PIX IN | `transaction_created` | Cobrança PIX criada |
| PIX IN | `transaction_paid` | Cobrança PIX paga |
| PIX IN | `transaction_refunded` | Cobrança PIX estornada |
| PIX IN | `transaction_updated` | Cobrança PIX atualizada com outro status |
| PIX IN | `infraction` | Infração/MED criada ou atualizada |
| PIX OUT | `transfer_created` | Transferência PIX criada |
| PIX OUT | `transfer_updated` | Transferência PIX atualizada com outro status |
| PIX OUT | `transfer_completed` | Transferência PIX concluída |
| PIX OUT | `transfer_canceled` | Transferência PIX cancelada ou recusada |

Headers do callback: `Content-Type: application/json`, `User-Agent: PagzPay`,
`PagzPay-Signature: t={timestamp},v1={hash}` (HMAC-SHA256, quando o webhook tem `signatureSecret`).

```
GET    /api/v1/webhook           — Listar webhooks
POST   /api/v1/webhook           — Criar webhook  { url, events[] }
DELETE /api/v1/webhook/:id       — Remover webhook (irreversível)
```

Validação da assinatura (Node.js):

```javascript
const crypto = require('crypto')

function isValidPagzPayWebhook(payload, signature, secret) {
  const [timestampPart, signaturePart] = signature.split(',')
  const timestamp = timestampPart.replace('t=', '')
  const receivedSignature = signaturePart.replace('v1=', '')

  const expected = crypto
    .createHmac('sha256', secret)
    .update(`${timestamp}.${JSON.stringify(payload)}`)
    .digest('hex')

  return expected === receivedSignature
}
```

Payload de PIX IN (`transaction_created`/`transaction_paid`/`transaction_refunded`/`transaction_updated`):

```json
{
  "id": "8d0f3b8f-5c2f-4a10-8dd6-8bbf5e9b1d11",
  "type": "transaction",
  "event": "transaction_paid",
  "scope": "user",
  "transaction": {
    "id": "transaction_abc123",
    "amount": 5000,
    "status": "paid",
    "pix": {
      "endToEndId": "E1234567820241231000000000000001",
      "payerInfo": { "name": "Joao Silva", "document": "11122233344" }
    }
  }
}
```

Payload de PIX OUT (`transfer_created`/`transfer_updated`/`transfer_completed`/`transfer_canceled`):

```json
{
  "id": "5f9e0c94-60a4-4df8-bb81-7f6efc6f61b0",
  "type": "transfer",
  "event": "transfer_completed",
  "scope": "user",
  "transfer": {
    "id": "transfer_abc123",
    "amount": 10000,
    "status": "paid",
    "pix": {
      "endToEndId": "E1234567820241231000000000000002",
      "creditorAccount": { "name": "Fornecedor Exemplo", "document": "12345678000199" }
    }
  }
}
```

Payload de infração (`infraction`):

```json
{
  "id": "3db3ac40-a069-45e6-8cb8-6d55d09f516d",
  "type": "infraction",
  "event": "infraction",
  "scope": "user",
  "infraction": {
    "id": "infraction_abc123",
    "amount": 5000,
    "status": "OPEN",
    "pix": { "endToEndId": "E1234567820241231000000000000001" }
  },
  "transaction": {
    "id": "transaction_abc123",
    "amount": 5000,
    "status": "infraction",
    "pix": {
      "endToEndId": "E1234567820241231000000000000001",
      "payerInfo": { "name": "Joao Silva", "document": "11122233344" }
    }
  }
}
```

## Infrações (MED)

O MED (Mecanismo Especial de Devolução) é o processo do Banco Central para contestar um PIX
recebido — por suspeita de fraude ou pedido de devolução do pagador. Quando uma cobrança PIX IN
da sua conta é contestada, o PagzPay registra uma **infração**, bloqueia o valor contestado e
avisa pelo webhook `infraction` a cada mudança.

| Tipo | Significado |
|---|---|
| `FRAUD` | Suspeita de fraude na cobrança. |
| `REFUND_REQUEST` | Pedido de devolução feito pelo pagador. |
| `REFUND_CANCELLED` | Pedido de devolução cancelado. |

| `infraction.status` | Significado | Aceita defesa? |
|---|---|---|
| `OPEN` | Infração aberta. O valor fica bloqueado. | Sim |
| `ACKNOWLEDGED` | Infração reconhecida pelo PSP; bloqueio mantido. | Sim |
| `WAITING_PSP` | Aguardando o PSP; bloqueio mantido. | Sim |
| `DEFENDED` | Defesa enviada, em análise. | Não |
| `ANSWERED` / `WAITING_ADJUSTMENTS` | Em andamento no PSP; bloqueio mantido. | Não |
| `CLOSED` | **Final.** Desfecho em `transaction.status` (abaixo). | Não |
| `CANCELLED` | **Final.** Infração cancelada; bloqueio liberado. | Não |

Efeito no saldo e na cobrança:

| Momento | `transaction.status` | Saldo |
|---|---|---|
| Infração aberta ou em andamento | `infraction` | O valor sai de `pix.balance` e aparece em `pixBlocked.balance`. |
| Encerrada a seu favor ou cancelada | `paid` | Bloqueio liberado; o valor volta para `pix.balance`. |
| Encerrada a favor do pagador | `refund` | O valor é devolvido ao pagador e o bloqueio é encerrado. |

> O payload não traz o resultado da análise. Para saber o desfecho de um `CLOSED`, use o
> `transaction.status` do mesmo evento: `paid` = valor liberado; `refund` = valor devolvido.

**Como se defender**

- **Onde:** pelo Dashboard, em **Pagamentos → Infrações**. Não há endpoint de defesa na API.
- **Quando:** enquanto o status for `OPEN`, `ACKNOWLEDGED` ou `WAITING_PSP`. O MED tem prazos
  regulatórios — envie assim que receber o webhook com `OPEN`.
- **O quê:** uma descrição (mínimo de 5 caracteres) e, se quiser, arquivos de comprovação.
- **Limite:** uma defesa por infração. Depois do envio o status vira `DEFENDED`.

O evento `infraction` é reenviado a cada mudança de status da mesma infração — use
`infraction.id` + `infraction.status` para processar cada transição uma única vez.

```javascript
function onInfraction({ infraction, transaction }) {
  const { status } = infraction

  if (['OPEN', 'ACKNOWLEDGED', 'WAITING_PSP'].includes(status)) {
    // Valor bloqueado: suspenda a entrega e envie a defesa pelo Dashboard
    return holdOrder(transaction.id)
  }

  if (status === 'CANCELLED' || (status === 'CLOSED' && transaction.status === 'paid')) {
    return releaseOrder(transaction.id) // valor liberado
  }

  if (status === 'CLOSED' && transaction.status === 'refund') {
    return cancelOrder(transaction.id) // valor devolvido ao pagador
  }
}
```

## Split de pagamentos

Distribui automaticamente parte do valor líquido de uma cobrança PIX IN para outras empresas
PagzPay — útil para marketplaces e plataformas com parceiros. Executado no momento em que o
pagamento é confirmado.

| Campo | Tipo | Obrig. | Descrição |
|---|---|---|---|
| `email` | string | Sim | E-mail da empresa PagzPay que recebe o split — precisa ter conta ativa. |
| `percentage` | number | Sim | De 0,01 a 100. A soma de todos os splits não pode ultrapassar 100%. |

```json
{
  "amountInCents": 20000,
  "description": "Venda marketplace #5678",
  "customer": { "documentType": "cpf", "document": "33344455566" },
  "splits": [
    { "email": "parceiro1@empresa.com", "percentage": 30 },
    { "email": "parceiro2@empresa.com", "percentage": 15 }
  ]
}
```

## Idempotência

Para transferências PIX, envie o header `x-idempotency-key` com um UUID único por operação.

| Situação | Comportamento |
|---|---|
| Mesma chave, retry de rede | Nenhuma transferência duplicada é criada. |
| Mesma chave reaproveitada com corpo diferente | `409 IDEMPOTENCY_CONFLICT`. |
| Sem cabeçalho | A requisição é rejeitada — o header é obrigatório em PIX OUT. |

## Erros

Erros sempre respondem em JSON com `error` (código) e `message` (legível). Alguns também trazem
`details`.

| Status | Código | Significado |
|---|---|---|
| 400 | `INVALID_REQUEST` | Corpo da requisição inválido — `message` aponta o campo. |
| 401 | `UNAUTHORIZED` | Token ausente, inválido ou expirado. |
| 404 | `NOT_FOUND` | Recurso não encontrado ou pertence a outra conta. |
| 409 | `IDEMPOTENCY_CONFLICT` | `x-idempotency-key` reaproveitada com corpo diferente. |
| 429 | `RATE_LIMITED` | Muitas requisições — tente novamente em alguns minutos. |

```json
{ "error": "NOT_FOUND", "message": "QR Code PIX não encontrado" }
```

## Referência rápida de endpoints

| Método | Rota | Descrição |
|---|---|---|
| POST | `/api/auth` | Gerar token de acesso |
| POST | `/api/v1/pix/in/qrcode` | Criar QR Code PIX |
| GET | `/api/v1/pix/in/qrcode/:id` | Consultar QR Code PIX |
| POST | `https://api.pagar.me/core/v5/tokens` | Tokenizar cartão (Pagar.me, browser) |
| POST | `/api/v1/card` | Criar cobrança cartão de crédito |
| GET | `/api/v1/transactions/:id` | Consultar transação (cartão, PIX, etc.) |
| GET | `/api/v1/transactions/:id/status` | Consultar só o status |
| POST | `/checkout/{token}/pay/card` | Pagar com cartão no checkout hospedado (sem Bearer) |
| GET | `/checkout/{token}/status/{transactionId}` | Status da transação do checkout |
| POST | `/api/v1/pix/out/pixkey` | Enviar transferência PIX |
| GET | `/api/v1/pix/out/pixkey/:id` | Consultar transferência PIX |
| GET | `/api/v1/balance` | Consultar saldo |
| GET | `/api/v1/webhook` | Listar webhooks |
| POST | `/api/v1/webhook` | Criar webhook |
| DELETE | `/api/v1/webhook/:id` | Remover webhook |

---

Spec completa (OpenAPI 3.1): [`openapi.yaml`](./openapi.yaml) / [`openapi.json`](./openapi.json)
