# Integração Partner API Monetarie: respostas às dúvidas da Vulci

Documento técnico preparado em 10/07/2026 para responder às dúvidas levantadas pelo Herbeth sobre a integração com a Partner API da Monetarie (base `/api/partner/v1`). O objetivo é deixar claro o que já existe e funciona hoje, o que ainda não existe, e por que a transferência de teste ficou parada sem notificação.

## 1. Ambiente, autenticação e formato

A Partner API vive no domínio `/api/partner/v1` e é escopada por parceiro. A autenticação é OAuth2: o parceiro obtém um token em `POST /api/partner/v1/oauth/token` e envia esse token como `Authorization: Bearer <token>` nas demais chamadas. Cada chave de API só enxerga as contas do próprio parceiro.

Dois pontos importantes de convenção:

1. Todos os valores monetários trafegam em centavos. Um PIX de R$ 1,00 é `amount: 100`, como no exemplo que vocês mandaram.
2. Requisições que criam ou movimentam dinheiro aceitam o header `Idempotency-Key`. Reenviar a mesma chave devolve o mesmo resultado, sem duplicar a operação.

A Vulci está integrando contra o ambiente de homologação. A transferência de teste que vocês citaram (`PIXOUT20260710fafa7e9913ac4461af2c`) está no ambiente de homologação, e não em produção. Isso é relevante para entender o comportamento descrito mais adiante.

## 2. Ciclo de vida de uma transação PIX e os status

Essa é a origem da confusão entre "aceito" e "processando", então vale detalhar.

Quando vocês chamam `POST /api/partner/v1/pix/payments`, a resposta imediata é um HTTP 202 com o corpo assim:

```json
{
  "status": "accepted",
  "transactionId": "PIXOUT...",
  "endToEndId": "E4602656220260710...",
  "amount": 100
}
```

Esse `"accepted"` na resposta da criação não é o status da transação. Ele é apenas o reconhecimento de que a API recebeu e enfileirou a ordem. Nesse mesmo instante, a transação é gravada no banco já com o status real igual a `processing`. Por isso, quando vocês consultam logo em seguida o endpoint de status, ele devolve `processing`. Não há inconsistência: `"accepted"` é o aceite do pedido pela API, e `processing` é o estado da transação em si.

O conjunto completo de status possíveis de uma transação PIX é fixo e vale a pena vocês mapearem no lado de vocês:

| Status | Significado | É final? |
|---|---|---|
| `processing` | Em andamento, aguardando a liquidação no SPI/BACEN | Não |
| `accepted` | Recebemos um aceite intermediário (ACSP), ainda sem confirmação final | Não |
| `settled` | Liquidada com sucesso no BACEN | Sim (sucesso) |
| `confirmed` | Confirmada | Sim (sucesso) |
| `completed` | Concluída | Sim (sucesso) |
| `rejected` | Rejeitada pelo BACEN ou pelo recebedor | Sim (falha) |
| `timeout` | Sem resposta dentro do prazo, cancelada e valor devolvido ao pagador | Sim (falha) |
| `cancelled` | Cancelada | Sim (falha) |
| `refunded` | Devolvida após liquidação | Sim |

Os estados de sucesso que encerram o fluxo são `settled`, `confirmed` e `completed`. Os de falha são `rejected`, `timeout` e `cancelled`. O `refunded` é o único estado que pode vir depois de um `settled`, no caso de devolução.

O que move a transação de `processing` para `settled` ou `rejected` não é a nossa API: é um evento que a cabine PIX publica quando o BACEN responde (a pacs.002 do fluxo SPI). Quando esse evento chega, o status é materializado para `settled` (e o `completedAt` é preenchido) ou para `rejected` (e os fundos retidos são liberados de volta para o pagador).

## 3. Por que a transferência de teste ficou só em "processing"

A transferência de vocês (`PIXOUT20260710fafa7e9913ac4461af2c`, E2E `E4602656220260710000900000000140`) está no ambiente de homologação, criada às 00:09 do dia 10/07, e permanece em `processing` porque a cabine nunca recebeu do BACEN de homologação a confirmação final de liquidação daquela ordem. Sem esse evento de liquidação, não há o que materializar, e a transação fica parada no estado intermediário.

Isso é esperado em homologação sempre que a liquidação no SPI não fecha, o que costuma acontecer por três motivos: o HSM de assinatura fora do ar, o canal RSFN/ICOM indisponível, ou a chave de destino de teste que não completa o fluxo no ambiente do BACEN homolog. Em produção, com o caminho de assinatura e liquidação ativos, o estado avança normalmente.

Existe uma rede de proteção para o caso de a transação ficar presa: um verificador roda de tempos em tempos e, quando encontra uma transação em `processing` há mais de 30 minutos sem resposta, muda o estado para `timeout` e devolve o valor retido ao pagador. No caso específico dessa transação de teste, ela ainda aparece como `processing` bem depois desse prazo, o que indica que esse verificador não atuou sobre ela em homologação. Vamos revisar esse ponto, porque a expectativa correta é que uma ordem sem liquidação vire `timeout` sozinha e libere o valor.

O ponto prático para vocês agora: como não existe webhook de saída para PIX (ver a seção seguinte), a forma de acompanhar o desfecho de uma transferência é consultar `GET /api/partner/v1/pix/payments/:id` até o status virar um dos estados finais.

## 4. Webhooks: o que dispara hoje e por que vocês não receberam nada

Aqui está o motivo central de vocês não terem recebido nenhuma notificação, e é preciso ser transparente sobre o estado atual.

### 4.1. Como registrar e o formato da entrega

O parceiro registra uma URL em `POST /api/partner/v1/webhooks`, informando a lista de eventos que quer assinar. O segredo de assinatura é gerado pela Monetarie e devolvido uma única vez no momento do cadastro (ou na rotação por `POST /api/partner/v1/webhooks/:id/rotate-secret`). O catálogo de eventos pode ser consultado em `GET /api/partner/v1/webhooks/events`.

Quando um evento dispara, a entrega é um HTTP POST na URL configurada. O corpo é o JSON do próprio evento, e os metadados vão nos headers:

- `X-Monetarie-Event-Type`: o tipo do evento (por exemplo `pix.charge.paid`).
- `X-Monetarie-Event-Id`: identificador único da entrega, útil para deduplicar.
- `X-Monetarie-Timestamp`: o instante da assinatura.
- `X-Monetarie-Signature`: `sha256=<hex>`, um HMAC-SHA256 calculado sobre a string `"{timestamp}.{corpo_json}"` usando o segredo do webhook. É assim que vocês validam a autenticidade.

As entregas têm retentativa automática com backoff (até 8 tentativas, de imediato até 4 horas). Respostas HTTP 400, 401, 403, 404, 410 e 422 são tratadas como falha permanente e não são retentadas.

### 4.2. A causa do "não chega webhook": eventos sem produtor e ausência de webhook para PIX de saída

Vocês cadastraram dois webhooks em homologação apontando para o webhook.site. Um deles assina eventos da família `pix.payout.*` (`pix.payout.queued`, `pix.payout.processing`, `pix.payout.confirmed`, e assim por diante). O problema é que, hoje, esses eventos de payout estão no catálogo mas não possuem produtor no código, ou seja, nenhum ponto do sistema chega a emiti-los. Por isso, mesmo assinados corretamente, eles nunca chegam.

Mais do que isso: hoje não existe webhook nenhum no ciclo de vida de um PIX de saída. Uma transferência que liquida com sucesso vira `settled` no banco, mas não gera notificação. Uma que falha vira `rejected` ou `timeout`, também sem notificação. A consequência direta é a que vocês observaram: a única forma de saber o desfecho de um PIX de saída hoje é consultar o endpoint de status por polling.

Para não deixar dúvida, esta é a lista dos eventos que de fato são emitidos hoje, contra os que aparecem no catálogo mas ainda não têm produtor:

| Evento | Dispara hoje? | Quando |
|---|---|---|
| `pix.charge.created` | Sim | Ao criar uma cobrança/QR dinâmico |
| `pix.charge.paid` | Sim | Recebimento de um PIX de entrada / QR pago |
| `pix.return.received` | Sim | Devolução recebida (pacs.004) |
| `pix.payout.returned` | Sim (condicional) | Devolução de um PIX que você enviou |
| `webhook.test` | Sim | Teste manual |
| `pix.payout.queued/processing/held/confirmed/failed` | Não | Sem produtor no código hoje |
| `pix.refund.requested/completed` | Não | Sem produtor no código hoje |
| `pix.infraction.*` | Não | Sem produtor no código hoje |

Existe ainda um segundo ponto que precisamos validar antes de vocês confiarem no recebimento: o roteamento interno dos eventos é escopado por tenant usando o `merchant_id` do webhook, enquanto os webhooks registrados via Partner API ficam vinculados pela chave de API, com `merchant_id` nulo. Na leitura do código, isso pode fazer com que um webhook de parceiro não receba os eventos escopados por conta (inclusive o `pix.charge.paid` de entrada). É um risco real e vamos confirmar empiricamente e corrigir, porque não adianta emitir o evento se ele não casa com o webhook do parceiro.

### 4.3. O que vamos entregar aqui

Para a integração de vocês funcionar como esperado, o combinado é entregarmos:

1. O evento de desfecho do PIX de saída, disparando `pix.payout.confirmed` na liquidação e `pix.payout.failed` na rejeição ou timeout, com o `endToEndId`, o status final e o motivo em caso de falha.
2. A correção do escopo de roteamento para que os webhooks registrados pela Partner API recebam de fato os eventos das contas do parceiro.
3. O evento de recebimento de fundos na conta (crédito PIX de entrada) chegando de forma confiável ao parceiro.

Enquanto esses eventos não estão no ar, o caminho suportado e correto é o polling do endpoint de status.

## 5. Recebimento de fundos na conta (PIX de entrada)

O evento previsto para o recebimento de fundos é o `pix.charge.paid`. Ele é emitido no crédito de entrada. Há uma ressalva honesta: no caminho de crédito que roda em produção hoje, esse evento nem sempre é emitido pela via principal, e por isso ele entra na lista de itens que vamos garantir na seção anterior. Assim que confirmado, o payload trará a conta creditada, o valor, o E2E e o horário.

## 6. QR Code e BR Code (copia e cola)

O que existe hoje:

- Gerar QR Code dinâmico com valor: `POST /api/partner/v1/pix/charges`. O corpo pede `account_id`, `amount` (em centavos), e opcionalmente `description` e `pix_key`. A resposta traz `brcode` (o payload EMV pronto, com CRC), `qrcode_base64` (a imagem PNG), `txId` e `expiresAt`. O TTL padrão é de 1 hora.
- Consultar a cobrança gerada: `GET /api/partner/v1/pix/charges/:id`, que devolve status, valor, brcode e o `paidAt` quando pago.

Uma observação técnica importante: essa cobrança é um QR montado localmente com valor fixo, e não uma cobrança imediata do tipo `cob`/`cobv` registrada no PSP com txid e location no BACEN. Se a necessidade de vocês incluir cobrança com vencimento, juros e multa (CobV), isso ainda não existe e precisa ser desenhado.

O que ainda não existe:

- Endpoint para consultar/decodificar um BR Code (o copia e cola), ou seja, receber a string EMV e devolver os campos já interpretados (valor, chave, nome do recebedor, txid). O parser interno existe, mas hoje ele só é usado dentro de um endpoint que decodifica e paga em uma única chamada, sem expor a leitura isolada. Vamos expor um endpoint de consulta e conversão do EMV para a leitura do QR, como vocês pediram.

## 7. Consulta de cliente por documento (CPF/CNPJ)

Hoje a consulta de cliente é feita por identificador interno em `GET /api/partner/v1/customers/:id`. Não existe ainda um endpoint que resolva o cliente ou a conta a partir do CPF ou CNPJ. Esse endpoint de busca por documento é um dos itens a construir.

## 8. Portabilidade de chave PIX

A consulta de chave no diretório (DICT) existe em `GET /api/partner/v1/pix/dict/:key`, e a gestão das chaves próprias (criar, listar, remover) também existe. O que não existe ainda é o fluxo de portabilidade e reivindicação de chave de outro PSP, nem o evento de webhook de solicitação de portabilidade. Ambos entram na lista de construção.

## 9. Boletos, limites e encerramento de conta

Três itens da lista de vocês ainda não têm endpoint na Partner API:

- Boletos (emissão de cobrança e pagamento). Alinhado com o que vocês colocaram, esse é o ponto para desenhar a integração com a Celcoin no futuro.
- Ajuste de limites da conta.
- Encerramento e bloqueio de conta.

Nenhum dos três está exposto hoje. São endpoints a construir, e podemos priorizar conforme a necessidade de vocês.

## 10. Modelo de tarifas e a correlação com a transação principal

Esse ponto é interessante porque o modelo de vocês (tarifa como transação separada) é compatível com o modelo interno da Monetarie, mas a correlação hoje não está exposta na Partner API.

No lado da Monetarie, quando uma transação gera tarifa, a tarifa é lançada como um registro separado no razão, e não como um campo dentro da transação principal. Esse registro de tarifa guarda a referência para a operação que o originou. A correlação é feita por valor, não por chave estrangeira:

- O registro de tarifa carrega `origin_transaction_id`, que aponta para o `transaction_id` da transação principal ou para o E2E da operação PIX.
- Carrega também `origin_transaction_type` (por exemplo `pix`, `pix_in`, `ted`), para você distinguir a natureza da operação de origem.

Ou seja, o conceito é o mesmo que vocês descreveram: a tarifa é uma transação própria de débito, correlacionada à principal por um campo de origem. A diferença é que, hoje, esses campos de tarifa não são devolvidos em nenhuma resposta da Partner API (nem o `fee_amount`, nem o `origin_transaction_id`). No caso de devolução, a transação de devolução aponta para a original por `refund_of_id`.

Para a integração, o combinado é: expormos na Partner API o campo de tarifa e um campo de correlação com a transação principal (equivalente ao `origin_transaction_id`), para que vocês consigam casar a tarifa com a operação que a gerou, tanto na saída quanto na entrada, além da tarifa avulsa de manutenção, que fica como uma transação de débito sem operação principal.

## 11. Resumo: o que existe e o que falta

| Item | Situação |
|---|---|
| Criar e consultar PIX de saída | Existe (`POST /pix/payments`, `GET /pix/payments/:id`) |
| Status da transação por polling | Existe e é o caminho recomendado hoje |
| Webhook de desfecho do PIX de saída | Não existe, vamos entregar |
| Webhook de recebimento de fundos | Previsto (`pix.charge.paid`), a garantir e validar escopo |
| Gerar QR Code dinâmico | Existe (`POST /pix/charges`) |
| Consultar/decodificar BR Code (EMV) | Não existe, vamos expor |
| Cobrança com vencimento (CobV) | Não existe |
| Consultar chave PIX (DICT) | Existe (`GET /pix/dict/:key`) |
| Consultar cliente por CPF/CNPJ | Não existe, vamos construir |
| Portabilidade de chave (endpoint e webhook) | Não existe, vamos construir |
| Boletos (cobrança e pagamento) | Não existe (desenho futuro com a Celcoin) |
| Ajuste de limites da conta | Não existe |
| Encerramento e bloqueio de conta | Não existe |
| Tarifa e correlação com a principal | Modelo existe internamente, falta expor na API |

## 12. Próximos passos que assumimos

1. Ligar os webhooks do ciclo de saída do PIX (`pix.payout.confirmed` e `pix.payout.failed`) e garantir o `pix.charge.paid` no recebimento, corrigindo o escopo de roteamento para webhooks de parceiro.
2. Revisar por que a transação de teste presa não virou `timeout` sozinha em homologação.
3. Expor o endpoint de consulta e conversão do BR Code (EMV) para leitura de QR.
4. Adicionar a consulta de cliente por CPF/CNPJ.
5. Expor os campos de tarifa e de correlação com a transação principal.
6. Sequenciar boletos (Celcoin), portabilidade de chave, ajuste de limites e encerramento/bloqueio de conta conforme a prioridade de vocês.

Qualquer ponto que precise de exemplo de payload concreto (corpo e headers de cada webhook, ou o JSON exato de resposta de cada endpoint) a gente monta e envia junto, para vocês ajustarem a solução sem depender de tentativa e erro.
