Transferências entre contas
Mova saldo para outra conta PayZu pela chave Pix dela, com o resultado já na resposta.
O dinheiro não passa pelo Pix: o destino é sempre outra conta PayZu, identificada pela chave Pix dela. Para qualquer outro destino, use um saque.
Pedir a transferência
POST /transactions/internal-transfer, escopo INTERNAL_TRANSFER. WITHDRAW não dá acesso a esta rota: a credencial precisa de INTERNAL_TRANSFER. Obrigatórios: amount, em centavos, e toPixKey, a chave Pix da outra conta PayZu.
Gere uma Idempotency-Key para cada transferência e, se repetir a chamada, mande a mesma.
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST https://api.hub.payzu.com.br/api/v1/transactions/internal-transfer \
-H "Authorization: Bearer $PAYZU_TOKEN" \
-H "Idempotency-Key: $IDEMPOTENCY_KEY" \
-H "Content-Type: application/json" \
-d '{
"amount": 10000,
"toPixKey": "financeiro@lojaparceira.com.br",
"comment": "Repasse do mês"
}'import crypto from 'node:crypto';
const idempotencyKey = crypto.randomUUID();
const res = await fetch('https://api.hub.payzu.com.br/api/v1/transactions/internal-transfer', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.PAYZU_TOKEN}`,
'Idempotency-Key': idempotencyKey,
'Content-Type': 'application/json',
},
body: JSON.stringify({
amount: 10000,
toPixKey: 'financeiro@lojaparceira.com.br',
comment: 'Repasse do mês',
}),
});
const transferencia = await res.json();comment aparece no comprovante. callbackUrl recebe os webhooks do seu lado e exige o segredo de callback. Todos os campos estão em Transferir para outra conta PayZu.
Ler o resultado
A resposta (201) traz o resultado. Com CONFIRMED, o valor já está na outra conta: não precisa esperar o webhook INTERNAL_TRANSFER_SENT.
{
"id": "hubp-20261005L9C3VH6KMA127431",
"status": "CONFIRMED",
"side": "SENT",
"amount": 10000,
"serviceFee": 100,
"totalDebited": 10100,
"counterparty": {
"name": "Loja Parceira Ltda",
"document": "12.345.678/0001-95",
"pixKey": "financeiro@lojaparceira.com.br"
},
"comment": "Repasse do mês",
"providerRejectedReason": null,
"callbackUrl": null,
"createdAt": "2026-10-05T15:02:44.010Z",
"confirmedAt": "2026-10-05T15:02:44.418Z",
"failedAt": null
}A tarifa é somada por cima: a outra conta recebe o amount inteiro, e da sua sai o totalDebited. Com FAILED, nada saiu, e providerRejectedReason traz uma mensagem fixa, para exibir.
Baixar o comprovante
Opcional. GET /transactions/internal-transfer/{transferId}/receipt devolve o PDF em base64, para transferência CONFIRMED. Não é comprovante de Pix e não tem end-to-end: a transferência é identificada pelo id.
Pedido repetido
A Idempotency-Key segue as regras do saque, e a comparação usa valor e destino. A mesma chave com outro valor ou destino é recusada com 409 TRANSFER_IDEMPOTENCY_KEY_REUSED.
Num 502 em que o banco não respondeu (PROVIDER_UNAVAILABLE) ou recusou de forma temporária (PROVIDER_REFUSED com details.status 408 ou 429), a transferência pode ter saído. Ela fica REQUESTED, sem resultado, e o valor fica fora do saldo disponível:
- A API não resolve sozinha: não há webhook nem consulta que antecipe o resultado. A PayZu confere com o banco, e a transferência passa a
CONFIRMEDouFAILED. - Repetir com a mesma
Idempotency-Keydevolve a original, aindaREQUESTED, sem enviar outra. Uma chave nova cria outra transferência. - Até o resultado ser conferido, ela conta no teto diário daquele dia.
Recusa definitiva do banco deixa a transferência FAILED e devolve o valor.
As duas pontas
A mesma transferência aparece nas duas contas, e side diz o lado:
| Campo | side: "SENT" | side: "RECEIVED" |
|---|---|---|
amount | O que saiu para o destino | O que entrou |
serviceFee e totalDebited | Tarifa e total debitado | 0 |
counterparty | Quem recebeu | Quem enviou |
counterparty.pixKey | Inteira na resposta do POST; mascarada na consulta e na listagem | Mascarada |
callbackUrl | A URL enviada | null |
| Webhook | INTERNAL_TRANSFER_SENT | INTERNAL_TRANSFER_RECEIVED |
Só a transferência confirmada gera webhook.
Consultar
GET /transactions/internal-transfer/{transferId}eGET /transactions/internal-transfer, escopoINTERNAL_TRANSFER_READ. A listagem traz as duas pontas;?side=SENTou?side=RECEIVEDfiltra uma. Também filtra porstatus,dateFromedateTo.- Transferência de outra conta, ou inexistente, responde
404TRANSFER_NOT_FOUND.
Limites
- Saldo insuficiente recusa a transferência inteira, e a conta é feita sobre o
totalDebited.TRANSFER_INSUFFICIENT_BALANCEtrazdetails.availableedetails.required. - O valor fica entre o mínimo e o máximo de transferência da conta, em
internalTransfernos limites. - O teto diário é próprio, separado do saque:
dailyInternalTransfer.0bloqueia toda transferência elimit: nullé sem teto. Estourar responde422TRANSFER_DAILY_LIMIT. - Até 5 pedidos por minuto por credencial e 10 por conta. Acima disso, a API responde
429comRetry-After.
Recusas
As que pedem outro destino ou um ajuste na conta:
| Status | code | Quando |
|---|---|---|
| 404 | TRANSFER_DESTINATION | Nenhuma conta PayZu tem esta chave ativa. Use um saque. |
| 422 | TRANSFER_DIFFERENT_PROVIDER | A conta de destino opera em outro banco. Use um saque. |
| 422 | TRANSFER_SAME_ACCOUNT | A chave é da sua própria conta. |
| 422 | TRANSFER_AMBIGUOUS_DESTINATION | A chave está ativa em mais de uma conta. |
| 422 | TRANSFER_DESTINATION_NOT_ACTIVE | A conta de destino não está ativa. |
| 422 | TRANSFER_MAIN_ACCOUNT_DESTINATION | A chave é de uma conta que não recebe transferência. |
| 422 | TRANSFER_NO_ORIGIN_KEY | A sua conta não tem chave Pix ativa. Veja Chaves Pix. |
| 422 | ACCOUNT_BLOCKED_BY_PROVIDER | O banco bloqueou a saída na sua conta ou a entrada na de destino. details.operation diz qual. |
| 422 | ACCOUNT_HELD_BY_STAFF | A sua conta ou a de destino está retida pelo suporte. |
Todas as recusas, com o code, estão em Transferir para outra conta PayZu, e as de credencial e escopo, em Autenticação.