Guia de Transferências Internas
Este guia explica como realizar transferências internas entre contas do mesmo banco, cobrindo os três métodos disponíveis e seus casos de uso.
Visão Geral
Transferências internas movem valores entre contas dentro do mesmo banco, sem passar pela rede PIX. São processadas instantaneamente.
Existem três formas de identificar a conta de destino:
| Método | Endpoint | Requisito da conta destino | Caso de uso |
|---|---|---|---|
| Por Account ID | /transfers/internal | Deve ter API habilitada | Transferências entre suas próprias contas |
| Por Documento | /transfers/internal/by-document | Qualquer conta do banco | Transferências para qualquer correntista |
| Por Agência/Conta | /transfers/internal/by-bank-account | Deve ter API habilitada | Quando você tem agência e número da conta |
1. Transferência por Account ID
O método mais performático. Use quando ambas as contas (origem e destino) estão registradas na API.
Endpoint: POST /v1/accounts/{accountId}/transfers/internal
curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/transfers/internal" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: int-001" \
-d '{
"destinationAccountId": "773107de-139e-48d1-9462-f4e88f251891",
"value": 1500.00,
"description": "Transferência entre filiais",
"identifier": "transf-filial-001"
}'
Campos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
destinationAccountId | string (UUID) | Sim | Account ID de destino (UUID interno da API) |
value | number | Sim | Valor em reais (máx. 2 casas decimais) |
description | string | Não | Descrição da transferência (máx. 140 caracteres) |
identifier | string | Não | Identificador único para rastreamento. Gerado automaticamente se omitido |
Resposta (200):
{
"paymentId": "pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"workflowId": "internal-out-{accountId}-transf-filial-001-int-001",
"runId": "b7c1f0e2-...",
"transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"endToEndId": "E50871921202603181530000000001",
"destinationAccountId": "773107de-139e-48d1-9462-f4e88f251891",
"status": "COMPLETED",
"value": 1500.00,
"description": "Transferência entre filiais",
"identifier": "transf-filial-001",
"idempotencyKey": "int-001",
"completedAt": "2026-03-18T15:30:00Z"
}
Quando a transferência é recusada, o mesmo corpo volta com 422,
status: "FAILED" e os campos errorCode/errorReason preenchidos.
2. Transferência por Documento (CPF/CNPJ)
Use quando a conta de destino não está registrada na API. Funciona para qualquer conta do ecossistema bancário.
Endpoint: POST /v1/accounts/{accountId}/transfers/internal/by-document
curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/transfers/internal/by-document" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: int-002" \
-d '{
"document": "12345678000190",
"value": 500.00,
"description": "Pagamento fornecedor",
"identifier": "pgto-fornecedor-002"
}'
Campos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
document | string | Sim | CPF ou CNPJ do titular da conta destino (somente números) |
value | number | Sim | Valor em reais |
description | string | Não | Descrição da transferência (máx. 140 caracteres) |
identifier | string | Não | Identificador único para rastreamento. Gerado automaticamente se omitido |
Quando o documento de destino possui mais de uma conta ativa, endereçar
por documento é ambíguo e a requisição é rejeitada com
409 multiple_destination_accounts. Nesse caso, use
/transfers/internal/by-bank-account (agência + conta) ou
/transfers/internal (destinationAccountId).
3. Transferência por Agência e Conta
Use quando você tem agência e número da conta. Ambas as contas devem estar registradas na API.
Endpoint: POST /v1/accounts/{accountId}/transfers/internal/by-bank-account
curl -X POST "https://tenant.api.corpx.com/v1/accounts/{accountId}/transfers/internal/by-bank-account" \
-H "Authorization: Bearer {token}" \
-H "X-Tenant-Id: tenant-suaempresa" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: int-003" \
-d '{
"branch": "0001",
"accountNumber": "200038274",
"holderDocument": "12345678000190",
"holderName": "EMPRESA DESTINO LTDA",
"value": 250.00,
"description": "Reembolso",
"identifier": "reembolso-003"
}'
Campos:
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
branch | string | Sim | Agência da conta destino |
accountNumber | string | Sim | Número da conta destino |
holderDocument | string | Sim | CPF/CNPJ do titular da conta destino — o liquidante exige o documento para rotear |
holderName | string | Não | Nome do titular destino |
value | number | Sim | Valor em reais |
description | string | Não | Descrição da transferência (máx. 140 caracteres) |
identifier | string | Não | Identificador único para rastreamento. Gerado automaticamente se omitido |
Qual método escolher?
Conta destino tem API habilitada?
├── SIM → Você tem o Account ID (UUID)?
│ ├── SIM → Use /transfers/internal (mais rápido)
│ └── NÃO → Use /transfers/internal/by-bank-account
└── NÃO → Use /transfers/internal/by-document
Exemplos práticos:
- Movimentação entre filiais da sua empresa →
/transfers/internal(por Account ID) - Pagamento a fornecedor do banco →
/transfers/internal/by-document(por CPF/CNPJ) - Transferência para conta conhecida por agência →
/transfers/internal/by-bank-account
Webhooks
Após uma transferência interna, ambas as partes recebem um webhook:
- Quem enviou:
transfer.internal.out - Quem recebeu:
transfer.internal.in
{
"id": "transfer-internal-out-pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"type": "transfer.internal.out",
"occurredAt": "2026-03-18T15:30:00.000000000Z",
"schemaVersion": "1.0",
"environment": "production",
"tenantId": "tenant-suaempresa",
"accountId": "a1b2c3d4-aaaa-bbbb-cccc-111122223333",
"data": {
"paymentId": "pay_2f4a0f88-2147-49f2-a4e2-4f7b9f6c0f7a",
"transactionId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"endToEnd": "E50871921202603181530000000001",
"accountId": "a1b2c3d4-aaaa-bbbb-cccc-111122223333",
"direction": "OUT",
"sourceAccountId": "a1b2c3d4-aaaa-bbbb-cccc-111122223333",
"sourceTenantId": "tenant-suaempresa",
"destinationAccountId": "773107de-139e-48d1-9462-f4e88f251891",
"destinationTenantId": "tenant-suaempresa",
"amount": 1500.00,
"description": "Repasse para filial SP",
"identifier": "transf-filial-001",
"status": "SUCCESS"
}
}
O data é plano: não existem objetos aninhados source/destination com
nome e documento — as contraparte aparecem como sourceAccountId /
destinationAccountId. A perna transfer.internal.in traz o mesmo payload com
accountId do destino e direction: "IN".
Como a liquidação é atômica e síncrona, o webhook sempre chega com data.status: "SUCCESS" — é o único valor possível para transfer.internal.*. Falhas (saldo insuficiente, conta destino inexistente, conta bloqueada, etc.) são sinalizadas na própria resposta HTTP da chamada POST /transfers/internal* e nunca geram webhook.
O webhook enviado ao pagador (transfer.internal.out) carrega identifier e description idênticos aos que você enviou no corpo da requisição (POST /transfers/internal*), junto com paymentId e transactionId, que também estão na resposta síncrona. Isso dispensa consultas adicionais ao extrato para fechar a transferência no seu sistema.
A perna transfer.internal.in só é emitida quando a conta de destino também é gerenciada na CorpX; ela repete os mesmos campos (inclusive o identifier definido pelo pagador), mudando apenas accountId e direction.
Para receber estes webhooks, inclua transfer.internal.in e/ou transfer.internal.out nos event types da sua subscrição.
Erros Comuns
| HTTP | errorCode | Causa |
|---|---|---|
| 400 | missing_fields | Campo obrigatório ausente (value, description, destino) |
| 400 | invalid_field | Valor com mais de 2 casas decimais, agência inválida, descrição fora do formato |
| 400 | invalid_identifier | identifier fora do charset [A-Za-z0-9._-] ou acima de 38 caracteres |
| 404 | beneficiary_not_found | Conta destino não existe (destinationAccountId desconhecido na CorpX ou documento sem conta no liquidante) |
| 422 | beneficiary_not_active_at_partner | Conta existe na CorpX, mas o liquidante ainda não tem o titular (credenciamento pendente) |
| 422 | beneficiary_incomplete | Liquidante devolveu o titular sem agência/conta |
| 422 | recipient_account_not_found | Liquidante não encontrou a conta destino informada |
| 409 | multiple_destination_accounts | Documento com mais de uma conta ativa (use by-bank-account) |
| 409 | identifier_conflict | Já existe transferência com esse identifier nesta conta de origem |
| 422 | insufficient_funds | Saldo disponível não cobre o valor |
| 422 | balance_reservation_failed | Saldo existe mas não pôde ser reservado (bloqueio, concorrência) |
| 422 | partner_account_disabled | Conta de origem desabilitada no banco |
| 422 | limit_exceeded_transaction / limit_exceeded_daily | Limite de transferência excedido |
| 422 | partner_rejected | Recusa por política interna do banco. O bloco partner traz o motivo informado |
| 502 | partner_error / partner_unavailable | Erro ou indisponibilidade no banco |
422 é desfecho, 202 é dúvidaUma transferência recusada responde 422 com status: "FAILED",
errorCode e errorReason — o dinheiro não saiu e não virá webhook algum.
O 202 com status: "PENDING" significa outra coisa: a chamada foi enviada
e a confirmação não chegou dentro da janela síncrona. Nesse caso consulte o
extrato antes de reemitir — repetir o POST com a mesma Idempotency-Key
reaproveita a operação em curso, mas com chave nova você pode transferir duas
vezes.