# Transferências entre contas (/docs/conta-digital/internal-transfers)

<QuickLinks>
  <QuickLink href="/docs/conta-digital/endpoints/internal-transfers/post_internal_transfer" title="Transferir para outra conta PayZu" method="POST" path="/transactions/internal-transfer" />

  <QuickLink href="/docs/conta-digital/endpoints/internal-transfers/get_internal_transfers" title="Listar transferências" method="GET" path="/transactions/internal-transfer" />
</QuickLinks>

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](/docs/conta-digital/withdrawals).

<Mermaid
  chart="`
flowchart LR
  A[&#x22;Pede a transferência&#x22;] --> B{&#x22;status na resposta&#x22;}
  B -->|&#x22;CONFIRMED&#x22;| C[&#x22;Valor na outra conta&#x22;]
  B -->|&#x22;FAILED&#x22;| D[&#x22;Nada saiu&#x22;]

  click A &#x22;/docs/conta-digital/endpoints/internal-transfers/post_internal_transfer&#x22; &#x22;Transferir para outra conta PayZu&#x22;

  style A fill:#f59e0b,stroke:#d97706,color:#ffffff
  style C fill:#14ce71,stroke:#0eb464,color:#ffffff
  style D fill:#ef4444,stroke:#dc2626,color:#ffffff
`"
/>

<Steps>
  <Step>
    ### Pedir a transferência [#pedir-a-transferência]

    [`POST /transactions/internal-transfer`](/docs/conta-digital/endpoints/internal-transfers/post_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.

    <Tabs items="['curl', 'Node.js']">
      <Tab value="curl">
        ```bash
        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"
          }'
        ```
      </Tab>

      <Tab value="Node.js">
        ```ts
        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();
        ```
      </Tab>
    </Tabs>

    `comment` aparece no comprovante. `callbackUrl` recebe os webhooks do seu lado e exige o [segredo de callback](/docs/conta-digital/webhooks#callbackurl-da-operação). Todos os campos estão em [Transferir para outra conta PayZu](/docs/conta-digital/endpoints/internal-transfers/post_internal_transfer).
  </Step>

  <Step>
    ### Ler o resultado [#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`.

    ```json
    {
      "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.
  </Step>

  <Step>
    ### Baixar o comprovante [#baixar-o-comprovante]

    Opcional. [`GET /transactions/internal-transfer/{transferId}/receipt`](/docs/conta-digital/endpoints/internal-transfers/get_internal_transfer_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`.
  </Step>
</Steps>

## Pedido repetido [#pedido-repetido]

A `Idempotency-Key` segue as regras do [saque](/docs/conta-digital/withdrawals#pedido-repetido), 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 `CONFIRMED` ou `FAILED`.
* Repetir com a mesma `Idempotency-Key` devolve a original, ainda `REQUESTED`, 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 [#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 [#consultar]

* [`GET /transactions/internal-transfer/{transferId}`](/docs/conta-digital/endpoints/internal-transfers/get_internal_transfer) e [`GET /transactions/internal-transfer`](/docs/conta-digital/endpoints/internal-transfers/get_internal_transfers), escopo `INTERNAL_TRANSFER_READ`. A listagem traz as duas pontas; `?side=SENT` ou `?side=RECEIVED` filtra uma. Também filtra por `status`, `dateFrom` e `dateTo`.
* Transferência de outra conta, ou inexistente, responde `404` `TRANSFER_NOT_FOUND`.

## Limites [#limites]

* Saldo insuficiente recusa a transferência inteira, e a conta é feita sobre o `totalDebited`. `TRANSFER_INSUFFICIENT_BALANCE` traz `details.available` e `details.required`.
* O valor fica entre o mínimo e o máximo de transferência da conta, em `internalTransfer` nos [limites](/docs/conta-digital/statement#limites).
* O teto diário é próprio, separado do saque: `dailyInternalTransfer`. `0` bloqueia toda transferência e `limit: null` é sem teto. Estourar responde `422` `TRANSFER_DAILY_LIMIT`.
* Até 5 pedidos por minuto por credencial e 10 por conta. Acima disso, a API responde `429` com `Retry-After`.

## Recusas [#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](/docs/conta-digital/pix-keys).           |
| 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](/docs/conta-digital/endpoints/internal-transfers/post_internal_transfer), e as de credencial e escopo, em [Autenticação](/docs/conta-digital/authentication#recusas-de-credencial).