# API Transakto: guia completo de integração

> Versão em Markdown de toda a documentação pública da API, pensada para ser lida por agentes de IA e ferramentas de geração de código. O conteúdo é o mesmo das páginas HTML em https://docs.transakto.io, sem navegação nem estilo.
>
> Collection do Postman pronta para importar: https://docs.transakto.io/postman-collection.json

## Resumo rápido para agentes

- URL base: `https://api.transakto.io/v1`
- Autenticação: header `x-api-key: <chave>` em toda chamada.
- Criações (`POST /v1/charges` e `POST /v1/payouts`) exigem o header `Idempotency-Key` (mínimo 8 caracteres, único por operação).
- Corpo e respostas em JSON (`Content-Type: application/json`).
- Valores monetários são inteiros em centavos: `4990` é R$ 49,90.
- Datas são strings ISO 8601 em UTC. Identificadores são UUID.
- Listagens devolvem `{ "object": "list", "data": [...], "has_more": bool }` e usam paginação por cursor (`limit`, `starting_after`).
- Erros devolvem `{ "code", "message", "details" }`. Trate sempre pelo `code`.
- Meio de pagamento disponível na v1: apenas `pix`.
- Para saber de pagamento, use `webhook_url` + `webhook_secret` na criação da cobrança, e valide `X-Webhook-Signature` (HMAC SHA-256 do corpo cru, prefixo `sha256=`).

### Endpoints

| Método | Caminho | Escopo | Idempotency-Key | Descrição |
| ------ | ------- | ------ | --------------- | --------- |
| POST | `/v1/charges` | `charge:write` | obrigatório | Criar cobrança Pix |
| GET | `/v1/charges/{id}` | `charge:read` | | Consultar cobrança |
| GET | `/v1/charges` | `charge:read` | | Listar cobranças |
| POST | `/v1/payouts` | `payout:write` | obrigatório | Criar transferência Pix |
| GET | `/v1/payouts/{id}` | `payout:read` | | Consultar transferência |
| GET | `/v1/payouts` | `payout:read` | | Listar transferências |
| GET | `/v1/balance` | `balance:read` | | Saldo da conta |
| GET | `/v1/account` | `account:read` | | Dados da conta |
| GET | `/v1/webhooks` | `webhook:read` | | Listar endpoints de webhook |
| POST | `/v1/webhooks` | `webhook:write` | | Cadastrar endpoint de webhook |
| PATCH | `/v1/webhooks/{id}` | `webhook:write` | | Atualizar endpoint de webhook |
| DELETE | `/v1/webhooks/{id}` | `webhook:write` | | Remover endpoint de webhook |
| GET | `/v1/webhooks/events` | nenhum | | Catálogo de eventos |

---

# Começando

## Introdução

A API de Transakto permite gerar cobranças Pix, enviar transferências e acompanhar o saldo da conta a partir do seu próprio sistema.

### URL base

Todos os endpoints públicos vivem sob o prefixo `/v1`.

```
https://api.transakto.io/v1
```

### Primeira chamada

Crie uma aplicação e uma chave em Configurações, API, na dashboard. A chave vai no header `x-api-key`. Requisições de escrita também exigem `Idempotency-Key`.

```bash
curl -X POST https://api.transakto.io/v1/charges \
  -H "x-api-key: sk_live_..." \
  -H "Idempotency-Key: pedido-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4990,
    "method": "pix",
    "reference": "pedido-1042",
    "customer": {
      "name": "Maria Souza",
      "email": "maria@exemplo.com.br",
      "document": "123.456.789-09"
    }
  }'
```

### Convenções

Valores monetários são inteiros em centavos: `4990` é R$ 49,90. Datas são strings ISO 8601 em UTC. Identificadores são UUID.

Toda listagem devolve um envelope `{ object: "list", data, has_more }` e usa paginação por cursor. Recursos individuais trazem o campo `object` dizendo o que são.

### O que existe na v1

Cobranças Pix, transferências Pix, consulta de saldo e de conta, além da gestão dos webhooks da conta.

> **Atenção:** boleto e cartão ainda não estão disponíveis pela API. Enviar `"method": "boleto"` ou `"card"` devolve `METHOD_NOT_SUPPORTED`.

### Ambientes

Não há ambiente de sandbox separado: cada instalação da plataforma tem sua própria URL, e as chaves valem apenas para a empresa que as gerou. Para testar sem risco, crie uma aplicação dedicada e revogue a chave ao terminar.

## Autenticação

Uma chave única por header. Sem OAuth, sem token de curta duração, sem troca de refresh.

### O header

Envie a chave em `x-api-key` em toda chamada a `/v1`. Sem ela, ou com uma chave revogada, expirada ou de uma aplicação desativada, a resposta é `401`.

```bash
curl https://api.transakto.io/v1/balance \
  -H "x-api-key: sk_live_..."
```

### Aplicações e chaves

Uma aplicação representa um sistema que consome a API e agrupa várias chaves. Isso existe para permitir rotação: você gera a chave nova, troca no seu sistema e revoga a antiga sem derrubar as outras integrações da mesma conta.

A chave completa aparece uma única vez, na resposta da criação. A plataforma guarda apenas o hash, então não existe endpoint que a releia. Se perder, revogue e gere outra.

### Escopos

Cada chave carrega uma lista de permissões. O endpoint recusa com `403` e código `INSUFFICIENT_SCOPE` quando falta alguma, informando qual.

| Escopo | Permite |
| ------ | ------- |
| `charge:read` | Consultar e listar cobranças. |
| `charge:write` | Criar cobranças. |
| `payout:read` | Consultar e listar transferências. |
| `payout:write` | Criar transferências. |
| `balance:read` | Consultar o saldo. |
| `account:read` | Consultar os dados da conta. |
| `webhook:read` | Listar os webhooks da conta. |
| `webhook:write` | Criar, editar e remover webhooks. |

Resposta 403:

```json
{
  "code": "INSUFFICIENT_SCOPE",
  "message": "A chave não tem permissão para esta operação.",
  "details": ["payout:write"]
}
```

### Boas práticas

Dê a cada chave só o que ela precisa: um sistema que apenas gera cobranças não deveria conseguir enviar dinheiro para fora. Guarde a chave em variável de ambiente ou cofre de segredos, nunca no código nem no front-end. Defina data de expiração para chaves de teste.

> **Atenção:** a chave dá acesso ao dinheiro da conta. Se ela vazar, revogue imediatamente pela dashboard: a revogação vale a partir da próxima requisição.

## Idempotência

Toda requisição que cria algo exige uma chave de idempotência. Repetir a chamada com a mesma chave devolve o recurso já criado em vez de criar outro.

### Como usar

Envie o header `Idempotency-Key` com um valor de ao menos 8 caracteres, único por operação. O identificador do seu lado costuma ser a melhor escolha: o número do pedido, o id da fatura, o id do lote de repasse.

```
-H "Idempotency-Key: pedido-1042"
```

Sem o header, a resposta é `400` com código `IDEMPOTENCY_KEY_REQUIRED`.

### Escopo da chave

A chave é única por aplicação. Duas aplicações da mesma conta podem usar `pedido-1042` sem colidir, e a mesma aplicação repetindo esse valor recebe de volta o mesmo recurso.

### Por que isso importa

Timeout de rede não distingue "a requisição não chegou" de "a resposta se perdeu". Sem idempotência, o retry seguro vira cobrança duplicada ou, pior, transferência duplicada. Com ela, o retry é sempre seguro: basta repetir a chamada inteira, com a mesma chave.

> **Nota:** use uma chave diferente para cada intenção distinta. Reaproveitar a chave de um pedido antigo em uma cobrança nova devolve a cobrança antiga, e o cliente pagaria o valor errado.

### Aplica-se a

`POST /v1/charges` e `POST /v1/payouts`. Rotas de leitura e a gestão de webhooks não pedem o header.

## Paginação

As listagens usam cursor, não offset. O cursor é o id do último item da página anterior.

### Parâmetros

| Parâmetro | Tipo | Descrição |
| --------- | ---- | --------- |
| `limit` | inteiro, 1 a 100 | Quantos itens trazer. Padrão 20. |
| `starting_after` | uuid | Id do último item da página anterior. A resposta começa no item seguinte a ele. |

### Envelope

Toda listagem devolve `object`, `data` e `has_more`. Não há contagem total: contar a tabela inteira a cada página seria caro e o número já estaria velho quando chegasse.

```json
{
  "object": "list",
  "data": [ { "id": "…", "object": "charge" } ],
  "has_more": true
}
```

### Percorrendo tudo

Repita enquanto `has_more` for verdadeiro, passando o id do último item.

```js
let cursor
const todas = []

do {
  const url = new URL('https://api.transakto.io/v1/charges')
  url.searchParams.set('limit', '100')
  if (cursor) url.searchParams.set('starting_after', cursor)

  const response = await fetch(url, {
    headers: { 'x-api-key': process.env.API_KEY },
  })
  const page = await response.json()

  todas.push(...page.data)
  cursor = page.data.at(-1)?.id
  var temMais = page.has_more
} while (temMais && cursor)
```

### Ordenação

Da mais recente para a mais antiga, por data de criação. Cobranças criadas enquanto você pagina não empurram itens para a página seguinte, que é justamente o problema do offset.

## Erros

Todo erro da API pública tem o mesmo formato: um código estável para o seu código ler e uma mensagem para humanos.

### Formato

```json
{
  "code": "INVALID_DOCUMENT",
  "message": "O CPF ou CNPJ informado é inválido.",
  "details": ["customer.document"]
}
```

Trate sempre pelo `code`. A `message` é texto de apresentação e pode mudar; `details` aparece quando há algo a listar, como os campos que falharam na validação.

### Códigos de status HTTP

| Status | Tipo | Significado |
| ------ | ---- | ----------- |
| 200 | sucesso | Leitura bem-sucedida. |
| 201 | sucesso | Recurso criado. |
| 204 | sucesso | Removido, sem corpo de resposta. |
| 400 | erro do cliente | Payload inválido ou regra de negócio recusada. |
| 401 | erro do cliente | Chave ausente, inválida, expirada ou revogada. |
| 403 | erro do cliente | A chave não tem o escopo necessário. |
| 404 | erro do cliente | O recurso não existe nesta conta. |
| 422 | erro do cliente | O adquirente recusou a operação. |
| 500 | erro do servidor | Falha inesperada. Pode ser repetida com a mesma chave de idempotência. |

### Códigos de erro (`code`)

| code | HTTP | Significado |
| ---- | ---- | ----------- |
| `UNAUTHORIZED` | 401 | Chave ausente ou não aceita. A mensagem é propositalmente vaga: dizer o motivo ajudaria quem está testando chaves. |
| `INSUFFICIENT_SCOPE` | 403 | Falta escopo. `details` traz os escopos exigidos. |
| `IDEMPOTENCY_KEY_REQUIRED` | 400 | Faltou o header `Idempotency-Key` na criação. |
| `VALIDATION_ERROR` | 400 | Campos inválidos no corpo. `details` lista o que falhou. |
| `INVALID_DOCUMENT` | 400 | CPF ou CNPJ inválido. |
| `METHOD_NOT_SUPPORTED` | 400 | O meio de pagamento pedido não está disponível na v1. |
| `AMOUNT_BELOW_MINIMUM` | 400 | Valor abaixo do mínimo da operação. |
| `INSUFFICIENT_BALANCE` | 400 | Saldo disponível menor que o valor mais a tarifa. |
| `CHARGE_NOT_FOUND` | 404 | Cobrança inexistente ou de outra conta. |
| `PAYOUT_NOT_FOUND` | 404 | Transferência inexistente ou de outra conta. |
| `WEBHOOK_NOT_FOUND` | 404 | Webhook inexistente ou de outra conta. |
| `INVALID_WEBHOOK_URL` | 422 | `webhook_url` (ou `url` do endpoint) não é http(s) ou aponta para localhost, IP privado ou link local. |
| `ACQUIRER_REJECTED` | 422 | O adquirente recusou. A mensagem repassa o motivo quando existe, e `charge_id` aponta a cobrança gravada como `failed`. |
| `INTERNAL_ERROR` | 500 | Falha inesperada do nosso lado. |

### Repetindo com segurança

Em `500` ou timeout, repita a chamada inteira com a mesma chave de idempotência. Em `4xx`, repetir não ajuda: corrija o payload primeiro.

## Limites de uso

### Situação atual

Não há limite numérico aplicado por chave na v1. Toda requisição a `/v1` é registrada com a chave usada, a rota, o status e a duração, e esse histórico é o que embasa o limite quando ele entrar.

> **Atenção:** ausência de limite não é permissão para laço apertado. Um integrador que consulta a mesma cobrança em intervalo de segundos pode ter a chave suspensa manualmente.

### Como consumir bem

Prefira webhook a polling: a mudança de estado de uma cobrança chega por evento, sem custo de consulta. Se precisar consultar, use intervalo crescente, começando em 30 segundos.

Nas listagens, use `limit=100` e o cursor em vez de várias páginas pequenas, e filtre por `created_after` para buscar só o que mudou desde a última sincronização.

### Quando o limite chegar

A resposta será `429`, no mesmo formato dos outros erros, e esta seção passará a documentar a janela e a cota. Trate `429` desde já com recuo exponencial: é a única resposta que vale a pena repetir sem mudar nada no payload.

---

# Cobranças

## Criar cobrança

Gera uma cobrança Pix e devolve o copia e cola para o pagador.

`POST /v1/charges` · escopo `charge:write`

Exige o header `Idempotency-Key`. Repetir a chamada com a mesma chave devolve a cobrança já criada.

### Corpo

| Campo | Tipo | Obrigatório | Descrição |
| ----- | ---- | ----------- | --------- |
| `amount` | inteiro (centavos) | sim | Mínimo 100, ou seja R$ 1,00. |
| `method` | string | não | Apenas `"pix"` na v1. Padrão `"pix"`. |
| `customer.name` | string | sim | Nome do pagador. |
| `customer.email` | string | sim | E-mail válido do pagador. |
| `customer.document` | string | sim | CPF ou CNPJ, com ou sem máscara. O adquirente exige. |
| `customer.phone` | string | não | Telefone do pagador. |
| `description` | string | não | Até 140 caracteres. Aparece na sua listagem de cobranças. |
| `reference` | string | não | Seu identificador. Volta em todas as respostas e eventos desta cobrança. |
| `expires_in` | inteiro (segundos) | não | De 60 a 604800 (7 dias). Sem isso, vale o padrão da conta. |
| `webhook_url` | string | não | Recebe todos os eventos desta cobrança, sem cadastro prévio. É a forma recomendada de ser avisado do pagamento. Precisa ser http(s) e pública. |
| `webhook_secret` | string | não | Assina as entregas feitas a `webhook_url` (header `X-Webhook-Signature`). |

### Exemplo

```bash
curl -X POST https://api.transakto.io/v1/charges \
  -H "x-api-key: sk_live_..." \
  -H "Idempotency-Key: pedido-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4990,
    "method": "pix",
    "description": "Assinatura mensal",
    "reference": "pedido-1042",
    "expires_in": 3600,
    "customer": {
      "name": "Maria Souza",
      "email": "maria@exemplo.com.br",
      "document": "123.456.789-09"
    },
    "webhook_url": "https://meusistema.com.br/hooks/pagamentos",
    "webhook_secret": "um-segredo-bem-longo"
  }'
```

Resposta 201:

```json
{
  "id": "8f1c2a90-1f4b-4f0e-9d33-5a2c1b7e9a10",
  "object": "charge",
  "status": "pending",
  "amount": 4990,
  "currency": "BRL",
  "method": "pix",
  "description": "Assinatura mensal",
  "reference": "pedido-1042",
  "customer": {
    "name": "Maria Souza",
    "email": "maria@exemplo.com.br",
    "document": "12345678909"
  },
  "pix": {
    "br_code": "00020126580014BR.GOV.BCB.PIX...",
    "end_to_end_id": null
  },
  "receiver": {
    "name": "Loja Exemplo LTDA",
    "document": "12345678000199",
    "pix_key": "financeiro@exemplo.com.br",
    "pix_key_type": "EMAIL",
    "institution_name": "Banco Exemplo",
    "institution_ispb": "00000000",
    "branch": "0001",
    "account": "1234567",
    "account_type": "CACC"
  },
  "webhook_url": "https://meusistema.com.br/hooks/pagamentos",
  "expires_at": "2026-07-28T15:00:00.000Z",
  "paid_at": null,
  "created_at": "2026-07-28T14:00:00.000Z"
}
```

### Mostrando o Pix ao pagador

`pix.br_code` é o payload EMV. Exiba como texto para copiar e como QR Code gerado a partir dessa mesma string. Não há URL de imagem: o QR é derivado do código, e gerá-lo no seu lado evita depender de outra chamada de rede na hora do checkout.

### Erros comuns

`INVALID_DOCUMENT` quando o CPF ou CNPJ não passa na validação de dígito. `METHOD_NOT_SUPPORTED` para boleto ou cartão. `ACQUIRER_REJECTED` quando o adquirente recusa: a mensagem repassa o motivo que ele devolveu.

A cobrança é gravada antes de ir ao adquirente. Mesmo recusada, ela existe: o corpo do `422` traz o `charge_id`, e consultar esse id devolve a cobrança com status `failed` e o motivo em `failure_reason`. Repetir o pedido com a mesma `Idempotency-Key` devolve essa mesma cobrança falha. Para tentar de novo, use uma chave nova.

> **Nota:** a cobrança criada pela API aparece na dashboard junto com as do checkout, marcada com origem API.

## Consultar cobrança

Lê uma cobrança pelo id devolvido na criação.

`GET /v1/charges/{id}` · escopo `charge:read`

```bash
curl https://api.transakto.io/v1/charges/8f1c2a90-1f4b-4f0e-9d33-5a2c1b7e9a10 \
  -H "x-api-key: sk_live_..."
```

### Estados (`status`)

| Valor | Significado |
| ----- | ----------- |
| `processing` | Gravada, aguardando o adquirente devolver o Pix. Dura segundos: termina em `pending` ou `failed`. |
| `pending` | Criada, aguardando pagamento. |
| `paid` | Paga e confirmada pelo adquirente. |
| `expired` | Passou de `expires_at` sem pagamento. |
| `canceled` | Cancelada antes do pagamento. |
| `failed` | O adquirente recusou a emissão ou o pagamento. O motivo vem em `failure_reason`. |
| `refunded` | Valor devolvido ao pagador. |
| `charged_back` | Contestada pelo pagador. |
| `infraction` | Pix pago com infração aberta no MED. Ainda sem desfecho: termina em `charged_back` (devolvido) ou volta a `paid`. |

`expired` é derivado de `expires_at` no momento da leitura: uma cobrança pendente cuja data já passou é devolvida como expirada, sem depender de rotina de varredura.

### Campos da resposta (`charge`)

| Campo | Tipo | Descrição |
| ----- | ---- | --------- |
| `id` | uuid | Identificador da cobrança. |
| `object` | string | Sempre `"charge"`. |
| `status` | string | Ver tabela de estados. |
| `amount` | inteiro | Valor em centavos. |
| `currency` | string | `"BRL"`. |
| `method` | string | Meio de pagamento: `pix` na v1. |
| `description` | string \| null | Descrição enviada na criação. |
| `reference` | string \| null | O identificador que você enviou. |
| `customer` | objeto \| null | Nome, e-mail e documento do pagador. |
| `pix.br_code` | string \| null | Copia e cola do Pix. |
| `pix.end_to_end_id` | string \| null | Identificador da liquidação, preenchido após o pagamento. |
| `receiver` | objeto \| null | Dados da conta que recebeu: nome, documento, chave Pix, instituição, ISPB, agência e conta. `null` quando o adquirente não informa, e cada campo interno pode vir `null`. |
| `webhook_url` | string \| null | A URL de webhook gravada na criação. O segredo nunca volta. |
| `expires_at` | ISO 8601 \| null | Quando a cobrança deixa de aceitar pagamento. |
| `paid_at` | ISO 8601 \| null | Quando o pagamento foi confirmado. |
| `failure_reason` | string \| null | Por que a emissão falhou, quando o status é `failed`. `null` nos demais casos. |
| `created_at` | ISO 8601 | Quando a cobrança foi criada. |

### Não encontrado

Cobranças criadas pelo checkout não são visíveis por aqui: a API pública enxerga apenas o que ela mesma criou. Consultar o id de uma delas devolve `404` com `CHARGE_NOT_FOUND`, o mesmo retorno de um id de outra conta.

## Listar cobranças

Devolve as cobranças criadas pela API, da mais recente para a mais antiga.

`GET /v1/charges` · escopo `charge:read`

### Query

| Parâmetro | Tipo | Descrição |
| --------- | ---- | --------- |
| `limit` | inteiro, 1 a 100 | Padrão 20. |
| `starting_after` | uuid | Id do último item da página anterior. |
| `status` | string | Filtra por estado: `processing`, `pending`, `paid`, `expired`, `canceled`, `failed`, `refunded`, `charged_back`, `infraction`. |
| `method` | string | Filtra por meio de pagamento. |
| `created_after` | ISO 8601 | Só cobranças criadas depois deste instante. |
| `created_before` | ISO 8601 | Só cobranças criadas antes deste instante. |

```bash
curl "https://api.transakto.io/v1/charges?status=paid&limit=50" \
  -H "x-api-key: sk_live_..."
```

Resposta 200:

```json
{
  "object": "list",
  "data": [
    {
      "id": "8f1c2a90-1f4b-4f0e-9d33-5a2c1b7e9a10",
      "object": "charge",
      "status": "paid",
      "amount": 4990,
      "currency": "BRL",
      "method": "pix",
      "reference": "pedido-1042",
      "paid_at": "2026-07-28T14:12:31.000Z",
      "created_at": "2026-07-28T14:00:00.000Z"
    }
  ],
  "has_more": false
}
```

### Conciliação

Para sincronizar o que mudou desde a última rodada, filtre por `created_after` com o instante da execução anterior e pagine até `has_more` ficar falso. Para acompanhar pagamento, prefira o webhook: a listagem serve para fechar o dia, não para descobrir que alguém acabou de pagar.

---

# Transferências

## Criar transferência

Envia dinheiro do saldo da conta para uma chave Pix.

`POST /v1/payouts` · escopo `payout:write`

Exige o header `Idempotency-Key`. Em transferência isso não é detalhe: sem a chave, um retry por timeout enviaria o dinheiro duas vezes.

### Corpo

| Campo | Tipo | Obrigatório | Descrição |
| ----- | ---- | ----------- | --------- |
| `amount` | inteiro (centavos) | sim | Mínimo 500, ou seja R$ 5,00. |
| `pix_key_type` | string | sim | `cpf`, `cnpj`, `email`, `phone` ou `random`. |
| `pix_key` | string | sim | A chave que recebe o valor. |
| `method` | string | não | Apenas `"pix"` na v1. Padrão `"pix"`. |
| `recipient_name` | string | não | Nome de quem recebe. |
| `recipient_document` | string | não | CPF ou CNPJ de quem recebe. |
| `description` | string | não | Aparece no seu extrato de transferências. |
| `reference` | string | não | Seu identificador, devolvido nas respostas e eventos. |
| `webhook_url` | string | não | Recebe todos os eventos desta transferência, sem cadastro prévio. Precisa ser http(s) e pública. |
| `webhook_secret` | string | não | Assina as entregas feitas a `webhook_url` (header `X-Webhook-Signature`). |

### Exemplo

```bash
curl -X POST https://api.transakto.io/v1/payouts \
  -H "x-api-key: sk_live_..." \
  -H "Idempotency-Key: repasse-2026-07-28-lote-3" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 150000,
    "pix_key_type": "cnpj",
    "pix_key": "12.345.678/0001-95",
    "recipient_name": "Fornecedor LTDA",
    "description": "Repasse semanal",
    "reference": "lote-3",
    "webhook_url": "https://meusistema.com.br/hooks/repasses",
    "webhook_secret": "um-segredo-bem-longo"
  }'
```

Resposta 201:

```json
{
  "id": "3b7a11d0-7e5c-4c6a-9d21-0e4f5a6b7c8d",
  "object": "payout",
  "status": "pending",
  "method": "pix",
  "amount": 150000,
  "fee": 0,
  "currency": "BRL",
  "description": "Repasse semanal",
  "reference": "lote-3",
  "recipient": {
    "name": "Fornecedor LTDA",
    "document": null,
    "pix_key_type": "cnpj",
    "pix_key": "12.345.678/0001-95"
  },
  "end_to_end_id": null,
  "failure_reason": null,
  "webhook_url": "https://meusistema.com.br/hooks/repasses",
  "processed_at": null,
  "created_at": "2026-07-28T14:00:00.000Z"
}
```

### Dois modos de execução

O que acontece depois do `201` depende de uma configuração da conta (`auto_payout_enabled`, lido em `GET /v1/account`).

Com envio automático desativado, a transferência fica em `pending`, na fila de aprovação manual da dashboard. Alguém precisa liberar. É o padrão, e o modo certo para quem move valores altos.

Com envio automático ativado, a resposta da criação já volta como `processing` e o envio ao adquirente acontece logo em seguida, sem intervenção. O desfecho (`completed` ou `failed`) chega pelo webhook.

> **Atenção:** em qualquer um dos modos, o valor mais a tarifa saem do saldo disponível no momento da criação. Se a transferência falhar, o valor volta e o estado vira `failed` com `failure_reason` preenchido.

### Erros comuns

`AMOUNT_BELOW_MINIMUM` abaixo de R$ 5,00. `INSUFFICIENT_BALANCE` quando o disponível não cobre valor mais tarifa: consulte `/v1/balance` antes de montar lotes grandes.

## Consultar transferência

Lê uma transferência pelo id, com o estado atual e o motivo da falha quando houver.

`GET /v1/payouts/{id}` · escopo `payout:read`

```bash
curl https://api.transakto.io/v1/payouts/3b7a11d0-7e5c-4c6a-9d21-0e4f5a6b7c8d \
  -H "x-api-key: sk_live_..."
```

### Estados (`status`)

| Valor | Significado |
| ----- | ----------- |
| `pending` | Criada. Aguardando aprovação manual, quando o envio automático está desativado. |
| `processing` | Enviada ao adquirente, aguardando liquidação. |
| `completed` | Dinheiro entregue. `end_to_end_id` preenchido. |
| `failed` | Recusada ou falhou. O valor volta ao saldo e `failure_reason` explica. |
| `canceled` | Cancelada antes do envio. O valor volta ao saldo. |

Uma transferência em `pending` por muito tempo não é sinal de erro: significa que a conta exige aprovação manual e ninguém aprovou ainda.

### Campos da resposta (`payout`)

| Campo | Tipo | Descrição |
| ----- | ---- | --------- |
| `id` | uuid | Identificador da transferência. |
| `object` | string | Sempre `"payout"`. |
| `status` | string | Ver tabela de estados. |
| `method` | string | `pix` na v1. |
| `amount` | inteiro | Valor em centavos, sem a tarifa. |
| `fee` | inteiro | Tarifa cobrada, em centavos. Sai do saldo junto com o valor. |
| `currency` | string | `"BRL"`. |
| `description` | string \| null | Descrição enviada na criação. |
| `reference` | string \| null | O identificador que você enviou. |
| `recipient` | objeto | Nome, documento, tipo e valor da chave Pix. |
| `end_to_end_id` | string \| null | Identificador da liquidação no Pix, disponível após `completed`. |
| `failure_reason` | string \| null | Motivo da recusa, quando o estado é `failed`. |
| `webhook_url` | string \| null | A URL de webhook gravada na criação. |
| `processed_at` | ISO 8601 \| null | Quando saiu para o adquirente. |
| `created_at` | ISO 8601 | Quando a transferência foi criada. |

## Listar transferências

Devolve as transferências da conta, da mais recente para a mais antiga.

`GET /v1/payouts` · escopo `payout:read`

### Query

| Parâmetro | Tipo | Descrição |
| --------- | ---- | --------- |
| `limit` | inteiro, 1 a 100 | Padrão 20. |
| `starting_after` | uuid | Id do último item da página anterior. |
| `status` | string | `pending`, `processing`, `completed`, `failed` ou `canceled`. |
| `method` | string | Filtra por meio: `pix` ou `ted`. |
| `created_after` | ISO 8601 | Só transferências criadas depois deste instante. |
| `created_before` | ISO 8601 | Só transferências criadas antes deste instante. |

```bash
curl "https://api.transakto.io/v1/payouts?status=pending" \
  -H "x-api-key: sk_live_..."
```

### Escopo dos resultados

Diferente das cobranças, esta listagem inclui também as transferências solicitadas pela dashboard.

> **Nota:** a razão é prática: elas consomem o mesmo saldo que a API reporta. Esconder as transferências feitas pela tela deixaria o extrato do integrador sem explicação para o dinheiro que sumiu.

---

# Conta

## Saldo

Quanto a conta tem disponível agora e quanto ainda vai liberar.

`GET /v1/balance` · escopo `balance:read`

```bash
curl https://api.transakto.io/v1/balance \
  -H "x-api-key: sk_live_..."
```

Resposta 200:

```json
{
  "object": "balance",
  "currency": "BRL",
  "available": 872300,
  "pending": 145000,
  "total": 1017300
}
```

| Campo | Tipo | Descrição |
| ----- | ---- | --------- |
| `available` | inteiro | Gastável agora, já descontadas as transferências em andamento. |
| `pending` | inteiro | Recebido que ainda não liberou. |
| `total` | inteiro | A soma dos dois. |

### Antes de transferir

Compare com `available`, não com `total`, e lembre da tarifa: o que sai do saldo é o valor mais a tarifa da transferência. Ainda assim, trate `INSUFFICIENT_BALANCE` no seu código: entre a consulta e o envio, outra operação pode ter consumido o saldo.

## Dados da conta

Identifica a conta dona da chave usada na requisição.

`GET /v1/account` · escopo `account:read`

```bash
curl https://api.transakto.io/v1/account \
  -H "x-api-key: sk_live_..."
```

Resposta 200:

```json
{
  "id": "1c9d4f22-7a3e-4b58-8f10-2d6b9c0e3a41",
  "object": "account",
  "name": "Loja Exemplo",
  "legal_name": "Exemplo Comércio LTDA",
  "document": "12345678000195",
  "document_type": "CNPJ",
  "phone": "+5511900000000",
  "status": "active",
  "onboarding_status": "COMPLETED",
  "auto_payout_enabled": false,
  "created_at": "2026-01-14T10:22:00.000Z"
}
```

| Campo | Tipo | Descrição |
| ----- | ---- | --------- |
| `status` | string | `active` ou `inactive`. Uma conta inativa não processa cobranças. |
| `onboarding_status` | string | Estágio do cadastro. Contas sem onboarding concluído têm restrições. |
| `auto_payout_enabled` | booleano | Se as transferências criadas pela API seguem direto para processamento ou entram na fila de aprovação manual. |

### Uso típico

Serve para validar credencial na configuração da integração: uma chamada barata que confirma que a chave é válida e mostra de qual conta ela é. Também é como o seu sistema descobre o modo de transferência sem perguntar ao usuário, lendo `auto_payout_enabled`.

---

# Webhooks

## Receber eventos (webhook_url por recurso)

Envie `webhook_url` na criação da cobrança ou da transferência e receba os eventos dela, sem cadastrar nada antes.

É o jeito recomendado de integrar. Cada cobrança e cada transferência carrega a URL que deve ser avisada, então o seu sistema decide o destino na hora de criar o recurso: um pedido da loja A vai para o backend da loja A, um repasse vai para o financeiro, sem configuração na dashboard.

### Como usar

| Campo na criação | Tipo | Descrição |
| ---------------- | ---- | --------- |
| `webhook_url` | string | URL http(s) pública que recebe os POSTs. Até 2048 caracteres. |
| `webhook_secret` | string | Assina as entregas para essa URL com o header `X-Webhook-Signature`. |

```bash
curl -X POST https://api.transakto.io/v1/charges \
  -H "x-api-key: sk_live_..." \
  -H "Idempotency-Key: pedido-1042" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 4990,
    "reference": "pedido-1042",
    "customer": {
      "name": "Maria Souza",
      "email": "maria@exemplo.com.br",
      "document": "123.456.789-09"
    },
    "webhook_url": "https://meusistema.com.br/hooks/pagamentos",
    "webhook_secret": "um-segredo-bem-longo"
  }'
```

Funciona igual em `POST /v1/payouts`. A URL volta no campo `webhook_url` das respostas, para você conferir o que ficou gravado. O segredo nunca volta.

### O que chega

Todos os eventos daquele recurso, do `created` ao desfecho: para uma cobrança Pix, `charge.pix.created`, `charge.pix.emitted` e depois `charge.pix.paid` (ou `failed`, `refunded` e os demais). Não há lista de eventos para escolher: se aconteceu com o recurso, a URL fica sabendo.

O corpo, os headers e a assinatura são os mesmos de qualquer entrega. Veja a seção Eventos para o formato e a seção Assinatura para validar o `X-Webhook-Signature`. O campo `reference` vem em todos os eventos, então dá para achar o pedido sem guardar o id da cobrança.

> **Atenção:** sem `webhook_secret` não há assinatura, e quem descobrir a URL consegue simular um pagamento confirmado. Em produção, mande sempre o segredo.

### Regras

A URL precisa ser `http` ou `https` e apontar para um host público. `localhost`, IPs de rede privada e endereços de link local são recusados na criação com `422` e `INVALID_WEBHOOK_URL`, antes de qualquer cobrança ser emitida. Para testar na sua máquina, exponha a porta com um túnel (ngrok, cloudflared) e use a URL pública dele.

A URL fica fixa no recurso: não dá para trocar depois de criado. Se o seu endereço mudar, as cobranças novas já saem com o novo, e as entregas antigas podem ser reenviadas pela dashboard.

### Entregas e retentativas

Responda com qualquer status `2xx` em até 10 segundos. Outra resposta, timeout ou erro de conexão conta como falha, e a entrega é repetida em 1 minuto, 5 minutos, 15 minutos, 1 hora, 3 horas, 6 horas e 12 horas: são 8 tentativas no total, ao longo de cerca de 22 horas. Depois disso a entrega fica como falha e pode ser reenviada pela dashboard.

Toda repetição leva o mesmo `id` de evento. Use esse id para ignorar o que já processou. As entregas saem em paralelo e não há garantia de ordem entre eventos: confie no `status` do recurso que vem no corpo, não na ordem de chegada.

### Junto com endpoints cadastrados

Se a sua aplicação também tem endpoints cadastrados em `/v1/webhooks`, eles continuam recebendo os eventos que assinaram. As duas formas somam: o `webhook_url` do recurso recebe tudo daquele recurso, e o endpoint cadastrado recebe os eventos escolhidos de todos os recursos.

Quando o `webhook_url` é exatamente a mesma URL de um endpoint cadastrado que já assina o evento, a entrega sai uma vez só, assinada com o segredo do endpoint cadastrado.

## Endpoints cadastrados

URLs fixas que recebem os eventos escolhidos de todas as cobranças e transferências da aplicação.

> **Nota:** para a maioria das integrações, enviar `webhook_url` na criação da cobrança é mais simples e não exige cadastro. O endpoint cadastrado serve para quem quer um destino único para tudo, ou receber eventos de cobranças criadas fora da API, como as do checkout.

São os mesmos endpoints que aparecem na dashboard, dentro da aplicação que emitiu a chave: o que você cadastra aqui é visível lá, e o contrário também. Cada aplicação vê apenas os próprios endpoints, então uma conta que opera duas lojas mantém as duas separadas usando uma aplicação para cada.

### Listar

`GET /v1/webhooks` · escopo `webhook:read`

Resposta 200:

```json
{
  "object": "list",
  "data": [
    {
      "id": "5e2f...",
      "object": "webhook",
      "url": "https://meusistema.com.br/hooks/pagamentos",
      "description": "Baixa de pedidos",
      "events": ["charge.pix.emitted", "charge.pix.paid"],
      "active": true,
      "has_secret": true,
      "created_at": "2026-07-28T14:00:00.000Z"
    }
  ]
}
```

O segredo nunca volta em resposta alguma: `has_secret` apenas informa que existe um configurado.

### Criar

`POST /v1/webhooks` · escopo `webhook:write` · não exige `Idempotency-Key`

| Campo | Tipo | Obrigatório | Descrição |
| ----- | ---- | ----------- | --------- |
| `url` | string | sim | Endereço que recebe os POSTs. |
| `events` | array de string | sim | Ao menos um evento, no formato `recurso.método.ação`. Ver a seção Eventos. |
| `description` | string | não | Para você reconhecer o endpoint na lista. |
| `secret` | string | não | Mínimo 8 caracteres. Passa a assinar as entregas. |

```bash
curl -X POST https://api.transakto.io/v1/webhooks \
  -H "x-api-key: sk_live_..." \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://meusistema.com.br/hooks/pagamentos",
    "events": ["charge.pix.paid", "charge.pix.refunded"],
    "description": "Baixa de pedidos",
    "secret": "um-segredo-bem-longo"
  }'
```

### Atualizar

`PATCH /v1/webhooks/{id}` · escopo `webhook:write`

Aceita `url`, `events`, `description`, `secret` e `active`. Enviar apenas `{ "active": false }` pausa as entregas sem apagar o cadastro, o que é o caminho certo durante uma manutenção do seu servidor.

### Remover

`DELETE /v1/webhooks/{id}` · escopo `webhook:write`

Responde `204`, sem corpo. Id inexistente, de outra conta ou de outra aplicação devolve `404` com `WEBHOOK_NOT_FOUND`.

### Catálogo de eventos

`GET /v1/webhooks/events` · escopo: nenhum

Devolve todos os eventos que a plataforma conhece, com as três partes do nome separadas, a descrição e o campo `dispatched`, que é falso enquanto a plataforma ainda não chega a emitir aquele evento. Não exige escopo: a lista é a mesma da documentação.

```json
{
  "object": "list",
  "data": [
    {
      "event": "charge.pix.paid",
      "resource": "charge",
      "method": "pix",
      "action": "paid",
      "description": "Pix liquidado. É o evento que dá baixa no pedido.",
      "dispatched": true
    }
  ]
}
```

## Eventos

O que a plataforma envia, e como o nome do evento já diz tudo.

### Como o nome é montado

Todo evento tem três partes, sempre nesta ordem: `<recurso>.<método>.<ação>`. Ler o nome responde as três perguntas que você faz ao receber a entrega: sobre o que é, por qual rail o dinheiro andou, e o que aconteceu.

```
charge.pix.paid
│      │   └── ação: o que aconteceu
│      └────── método: por qual rail
└───────────── recurso: sobre o que

charge.boleto.emitted   cobrança, boleto, instrumento pronto
payment.ted.completed   pagamento, TED, dinheiro creditado
```

| Recurso | Sentido | Descrição |
| ------- | ------- | --------- |
| `charge` | entrada | Cobrança: dinheiro entrando, pago por um cliente seu. |
| `payment` | saída | Pagamento: dinheiro saindo do seu saldo para uma conta externa. |

| Método | Recursos | Descrição |
| ------ | -------- | --------- |
| `pix` | charge, payment | Pix, nos dois sentidos. |
| `boleto` | charge | Boleto bancário. |
| `card` | charge | Cartão de crédito. |
| `ted` | payment | TED. |

> **Nota:** não existe evento genérico como `charge.paid`. O método faz parte do nome porque a régua de cobrança de um boleto não é a mesma de um Pix, e quem assina quase sempre quer tratar um sem tratar o outro. Para reagir a todos, assine os três: `charge.pix.paid`, `charge.boleto.paid`, `charge.card.paid`.

### Elegibilidade

Só existem as combinações que fazem sentido. Boleto não sofre chargeback, cartão não tem instrumento emitido nem vence, Pix não passa por autorização em duas etapas. Assinar um nome fora do catálogo devolve `422` no cadastro do endpoint, em vez de aceitar em silêncio um evento que nunca chegaria.

A lista completa também é servida pela API, em `GET /v1/webhooks/events`, com a descrição de cada evento e o campo `dispatched`.

### payment e o recurso payout

Os eventos `payment.*` descrevem o mesmo recurso servido em `/v1/payouts`. O nome do evento usa `payment` por simetria com `charge`: os dois lados do dinheiro, entrando e saindo, com a mesma gramática de nome.

### Catálogo completo

Coluna "Disparado": `sim` significa que a plataforma já emite o evento; `ainda não` significa que ele existe no catálogo e pode ser assinado, mas ainda não é emitido.

#### Cobrança via Pix (`charge.pix.*`)

| Evento | Disparado | Descrição |
| ------ | --------- | --------- |
| `charge.pix.created` | sim | Cobrança Pix registrada na plataforma. |
| `charge.pix.emitted` | sim | QR code e copia e cola disponíveis para o pagador. |
| `charge.pix.paid` | sim | Pix liquidado. É o evento que dá baixa no pedido. |
| `charge.pix.expired` | ainda não | QR code venceu sem pagamento. |
| `charge.pix.failed` | sim | A liquidação foi tentada e recusada pelo provedor. |
| `charge.pix.canceled` | sim | Cobrança cancelada antes do pagamento. |
| `charge.pix.refunded` | sim | Valor devolvido ao pagador. |
| `charge.pix.infraction` | sim | Infração aberta no MED: o pagador contestou o Pix e o desfecho está em aberto. |
| `charge.pix.charged_back` | sim | Devolução determinada pelo mecanismo especial de devolução (MED). |

#### Cobrança via boleto (`charge.boleto.*`)

| Evento | Disparado | Descrição |
| ------ | --------- | --------- |
| `charge.boleto.created` | sim | Cobrança por boleto registrada na plataforma. |
| `charge.boleto.emitted` | sim | Boleto emitido: linha digitável e PDF disponíveis. |
| `charge.boleto.paid` | sim | Boleto compensado pelo banco. |
| `charge.boleto.expired` | ainda não | Boleto venceu sem compensação. |
| `charge.boleto.failed` | sim | O banco recusou o registro ou a compensação. |
| `charge.boleto.canceled` | sim | Boleto baixado antes do pagamento. |
| `charge.boleto.refunded` | sim | Valor devolvido ao pagador. |

#### Cobrança via cartão (`charge.card.*`)

| Evento | Disparado | Descrição |
| ------ | --------- | --------- |
| `charge.card.created` | sim | Cobrança no cartão registrada na plataforma. |
| `charge.card.authorized` | ainda não | Emissor autorizou o valor, ainda sem captura. |
| `charge.card.paid` | sim | Valor capturado. É o evento que dá baixa no pedido. |
| `charge.card.failed` | sim | Compra recusada pelo emissor ou pelo antifraude. |
| `charge.card.canceled` | sim | Autorização cancelada antes da captura. |
| `charge.card.refunded` | sim | Estorno concedido ao portador. |
| `charge.card.charged_back` | sim | Compra contestada pelo portador junto ao emissor. |

#### Pagamento via Pix (`payment.pix.*`)

| Evento | Disparado | Descrição |
| ------ | --------- | --------- |
| `payment.pix.created` | sim | Pagamento Pix solicitado e na fila. |
| `payment.pix.processing` | sim | Enviado ao provedor, aguardando confirmação. |
| `payment.pix.completed` | ainda não | Dinheiro creditado na conta de destino. |
| `payment.pix.failed` | sim | Recusado pelo provedor. O valor volta ao saldo disponível. |
| `payment.pix.canceled` | sim | Cancelado antes de entrar em processamento. |

#### Pagamento via TED (`payment.ted.*`)

| Evento | Disparado | Descrição |
| ------ | --------- | --------- |
| `payment.ted.created` | sim | Pagamento por TED solicitado e na fila. |
| `payment.ted.processing` | sim | Enviado ao banco, aguardando confirmação. |
| `payment.ted.completed` | ainda não | Dinheiro creditado na conta de destino. |
| `payment.ted.failed` | sim | Recusado pelo banco. O valor volta ao saldo disponível. |
| `payment.ted.canceled` | sim | Cancelado antes de entrar em processamento. |

### Eventos ainda não disparados

Os marcados como "ainda não" existem no catálogo e podem ser assinados desde já: quando a plataforma passar a emiti-los, o endpoint começa a receber sem precisar mexer no cadastro. Até lá, acompanhe o estado por `GET /v1/charges/{id}` e `GET /v1/payouts/{id}`.

### created e emitted

Na criação de uma cobrança, Pix e boleto disparam os dois: `created` marca o registro na plataforma, `emitted` marca o instrumento pronto para o pagador. Hoje os dois acontecem no mesmo instante, porque a cobrança só é devolvida depois que o adquirente entregou o QR code ou a linha digitável.

Assine `created` se você só quer registrar o pedido, e `emitted` se o gatilho é mandar o QR code ou o boleto para o cliente. Assinar os dois faz o endpoint receber duas entregas.

> **Atenção:** cartão não tem `charge.card.emitted`: não há instrumento para entregar ao pagador. Use `charge.card.created`.

### Formato da entrega

A entrega é um `POST` com corpo JSON. Os headers carregam o evento inteiro e também as partes soltas, para quem roteia a mensagem para uma fila antes de abrir o corpo.

```
X-Webhook-Event: charge.pix.paid
X-Webhook-Resource: charge
X-Webhook-Method: pix
X-Webhook-Delivery: 4c1d9f80-2b7e-4a11-8c30-9f2a1b3c4d5e
X-Webhook-Signature: sha256=...
```

Corpo de um evento de cobrança:

```json
{
  "id": "4c1d9f80-2b7e-4a11-8c30-9f2a1b3c4d5e",
  "object": "event",
  "event": "charge.pix.paid",
  "resource": "charge",
  "method": "pix",
  "action": "paid",
  "created_at": "2026-07-28T14:03:11.000Z",
  "data": {
    "object": "charge",
    "id": "8f1c2a90-1f4b-4f0e-9d33-5a2c1b7e9a10",
    "status": "paid",
    "method": "pix",
    "amount": 4990,
    "currency": "BRL",
    "reference": "pedido-1042",
    "created_at": "2026-07-28T14:00:00.000Z"
  }
}
```

Corpo de um evento de pagamento:

```json
{
  "id": "b2790cf1-55a3-4a7d-9a02-1f6d4e8c3b77",
  "object": "event",
  "event": "payment.ted.created",
  "resource": "payment",
  "method": "ted",
  "action": "created",
  "created_at": "2026-07-28T15:10:00.000Z",
  "data": {
    "object": "payment",
    "id": "9d55a0b2-3c11-4e77-b0a1-72f9c4d10e3a",
    "status": "pending",
    "method": "ted",
    "amount": 250000,
    "currency": "BRL",
    "reference": "lote-3",
    "created_at": "2026-07-28T15:10:00.000Z"
  }
}
```

| Campo do envelope | Tipo | Descrição |
| ----------------- | ---- | --------- |
| `id` | string | Id da entrega. É o mesmo valor de `X-Webhook-Delivery`. |
| `event` | string | Nome completo, no formato `recurso.método.ação`. |
| `resource` | string | Primeira parte do nome, solta. |
| `method` | string | Segunda parte do nome, solta. |
| `action` | string | Terceira parte do nome, solta. Dá para dar switch sem fatiar string. |
| `created_at` | string | Quando a entrega foi montada, em ISO 8601 UTC. |
| `data` | objeto | A cobrança ou o pagamento, no mesmo formato do GET correspondente. |

> **Nota:** o valor vem em centavos inteiros, como no resto da API: `4990` são R$ 49,90. O `status` em `data` é o mesmo vocabulário de `GET /v1/charges` e `GET /v1/payouts`.

### Como responder

Responda `2xx` assim que receber, antes de processar. Todo o trabalho pesado, consulta a banco, envio de e-mail, deve acontecer depois da resposta: um handler lento vira timeout e a entrega é marcada como falha.

Trate as entregas como possivelmente repetidas. Use `X-Webhook-Delivery` para descartar o que já processou, ou torne o efeito idempotente pelo id em `data.id`.

## Assinatura

Como confirmar que a entrega veio mesmo da plataforma.

### O header

Se a cobrança foi criada com `webhook_secret`, ou o endpoint cadastrado tem `secret`, toda entrega chega com `X-Webhook-Signature`: o HMAC SHA-256 do corpo exato da requisição, em hexadecimal, prefixado por `sha256=`.

```
X-Webhook-Signature: sha256=6f1a...c93b
```

### Verificando

Calcule o HMAC sobre o corpo cru, antes de qualquer parse. Serializar o JSON de novo muda espaços e ordem de chaves, e a assinatura deixa de bater.

```js
import { createHmac, timingSafeEqual } from 'crypto'

app.post(
  '/hooks/pagamentos',
  express.raw({ type: 'application/json' }),
  (req, res) => {
    const recebida = req.header('X-Webhook-Signature') ?? ''
    const esperada =
      'sha256=' +
      createHmac('sha256', process.env.WEBHOOK_SECRET)
        .update(req.body)
        .digest('hex')

    const a = Buffer.from(recebida)
    const b = Buffer.from(esperada)

    if (a.length !== b.length || !timingSafeEqual(a, b)) {
      return res.sendStatus(401)
    }

    res.sendStatus(200)
    processarDepois(JSON.parse(req.body.toString('utf8')))
  },
)
```

Compare em tempo constante, com `timingSafeEqual` ou equivalente. Comparação com `===` vaza, pela diferença de tempo, quantos caracteres iniciais estavam certos.

### Boas práticas

Recuse a requisição quando a assinatura não bater, com `401`, e registre o caso: assinatura inválida é tentativa de forjar evento, não erro de rede.

> **Atenção:** sem `secret` cadastrado não há header de assinatura, e qualquer um que descubra a URL consegue simular um pagamento confirmado. Configure o segredo antes de colocar a integração no ar.

Para trocar o segredo sem perder entregas, cadastre um segundo endpoint com o segredo novo, valide que ele está recebendo e só então remova o antigo.

---

# Referência

## Changelog

Mudanças na API pública. Alterações que quebram contrato entram em uma versão nova, nunca na v1.

### Webhooks: webhook_url por cobrança passa a entregar

O `webhook_url` enviado em `POST /v1/charges` e `POST /v1/payouts` agora recebe todos os eventos daquele recurso, assinados com o `webhook_secret`. Não é preciso cadastrar endpoint antes, e essa passa a ser a forma recomendada de integrar.

URLs locais ou de rede privada são recusadas com `INVALID_WEBHOOK_URL`, inclusive no cadastro de endpoints. Os eventos de pagamento passam a trazer `reference`, e os de cobrança trazem `reference` também nas mudanças de status, não só na criação.

### Webhooks: nova taxonomia de eventos

Os eventos passaram a se chamar `<recurso>.<método>.<ação>`: `charge.pix.paid` no lugar de `charge.paid`, `charge.boleto.emitted`, `payment.ted.completed`. O método faz parte do nome, e só existem as combinações elegíveis.

O corpo da entrega passou a usar o vocabulário público do resto da API: envelope com `resource`, `method` e `action`, status igual ao de `GET /v1/charges` e valor em centavos inteiros. Os eventos `payout.*` viraram `payment.*` e agora são de fato entregues.

Endpoints já cadastrados foram convertidos: quem assinava `charge.paid` passou a assinar os três métodos, então continua recebendo o que recebia. O que muda no seu lado é o nome que chega em `X-Webhook-Event` e o formato do corpo.

### v1, versão inicial

Cobranças Pix em `/v1/charges`, transferências Pix em `/v1/payouts`, saldo em `/v1/balance`, dados da conta em `/v1/account` e gestão de webhooks em `/v1/webhooks`.

Autenticação por chave única no header `x-api-key`, com escopos por chave. Idempotência obrigatória nas criações. Paginação por cursor nas listagens.

### O que consideramos compatível

Podemos adicionar campos novos nas respostas, valores novos em enums e endpoints novos sem aviso. Escreva o cliente ignorando campos que não conhece e tratando valor de enum desconhecido como um caso genérico, em vez de estourar.

Não vamos remover campo, renomear campo, mudar tipo nem mudar o significado de um código de erro dentro da v1.

### Próximos passos

Entrega de `charge.*.expired`, `charge.card.authorized` e `payment.*.completed`, limite de requisições por chave e suporte a boleto e cartão nas cobranças.

---

# Receita de integração (passo a passo para agentes)

1. Leia a chave de `process.env` (nunca no código). Valide a credencial com `GET /v1/account` e guarde `auto_payout_enabled`.
2. Para cobrar: `POST /v1/charges` com `Idempotency-Key` igual ao id do pedido, `amount` em centavos, `customer` completo (nome, e-mail, CPF ou CNPJ), `reference` igual ao id do pedido, `webhook_url` pública e `webhook_secret`.
3. Mostre `pix.br_code` como copia e cola e gere o QR Code a partir dessa string no seu lado.
4. No endpoint de webhook: leia o corpo cru, valide `X-Webhook-Signature` em tempo constante, responda `200` imediatamente e processe depois. Descarte entregas repetidas pelo `X-Webhook-Delivery`.
5. Dê baixa no pedido ao receber `charge.pix.paid`, localizando o pedido por `data.reference`.
6. Para transferir: consulte `GET /v1/balance` (`available` menos a tarifa), depois `POST /v1/payouts` com `Idempotency-Key` estável por repasse. Acompanhe por `payment.pix.*` no `webhook_url`.
7. Em `500` ou timeout, repita a mesma chamada com a mesma `Idempotency-Key`. Em `4xx`, corrija o payload. Trate erros pelo `code`, nunca pela `message`.
8. Ignore campos desconhecidos nas respostas e trate valores de enum desconhecidos como caso genérico.
