PayZuDocs

Transfers between accounts

Move balance to another PayZu account by its Pix key, with the result already in the response.

The money does not go through Pix: the destination is always another PayZu account, identified by its Pix key. For any other destination, use a withdrawal.

Request the transfer

POST /transactions/internal-transfer, scope INTERNAL_TRANSFER. WITHDRAW does not give access to this route: the credential needs INTERNAL_TRANSFER. Required: amount, in cents, and toPixKey, the Pix key of the other PayZu account.

Generate an Idempotency-Key for each transfer and, if you repeat the call, send the same one.

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 appears on the receipt. callbackUrl receives the webhooks for your side and requires the callback secret. All fields are in Transfer to another PayZu account.

Read the result

The response (201) carries the result. With CONFIRMED, the amount is already in the other account: you do not need to wait for the INTERNAL_TRANSFER_SENT webhook.

{
  "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
}

The fee is added on top: the other account receives the full amount, and totalDebited leaves yours. With FAILED, nothing left the account, and providerRejectedReason carries a fixed message for display.

Download the receipt

Optional. GET /transactions/internal-transfer/{transferId}/receipt returns the PDF in base64, for a CONFIRMED transfer. It is not a Pix receipt and has no end-to-end ID: the transfer is identified by its id.

Repeated request

The Idempotency-Key follows the withdrawal rules, and the comparison uses amount and destination. The same key with another amount or destination is refused with 409 TRANSFER_IDEMPOTENCY_KEY_REUSED.

On a 502 where the bank did not respond (PROVIDER_UNAVAILABLE) or refused temporarily (PROVIDER_REFUSED with details.status 408 or 429), the transfer may have gone out. It stays REQUESTED, with no result, and the amount stays out of the available balance:

  • The API does not resolve it on its own: no webhook or lookup gives the result earlier. PayZu checks with the bank, and the transfer moves to CONFIRMED or FAILED.
  • Repeating with the same Idempotency-Key returns the original, still REQUESTED, without sending another one. A new key creates another transfer.
  • Until the result is checked, it counts toward that day's daily cap.

A final refusal from the bank leaves the transfer FAILED and returns the amount.

Both sides

The same transfer appears in both accounts, and side says which side:

Fieldside: "SENT"side: "RECEIVED"
amountWhat went out to the destinationWhat came in
serviceFee and totalDebitedFee and total debited0
counterpartyWho receivedWho sent
counterparty.pixKeyFull in the POST response; masked in the lookup and in the listMasked
callbackUrlThe URL sentnull
WebhookINTERNAL_TRANSFER_SENTINTERNAL_TRANSFER_RECEIVED

Only a confirmed transfer generates a webhook.

Look up

Limits

  • Insufficient balance refuses the whole transfer, and the check uses totalDebited. TRANSFER_INSUFFICIENT_BALANCE carries details.available and details.required.
  • The amount must be between the account's minimum and maximum transfer, in internalTransfer in the limits.
  • The daily cap is its own, separate from withdrawals: dailyInternalTransfer. 0 blocks every transfer and limit: null means no cap. Going over it responds 422 TRANSFER_DAILY_LIMIT.
  • Up to 5 requests per minute per credential and 10 per account. Above that, the API responds 429 with Retry-After.

Rejections

The ones that call for another destination or a change on the account:

StatuscodeWhen
404TRANSFER_DESTINATIONNo PayZu account has this key active. Use a withdrawal.
422TRANSFER_DIFFERENT_PROVIDERThe destination account operates at another bank. Use a withdrawal.
422TRANSFER_SAME_ACCOUNTThe key belongs to your own account.
422TRANSFER_AMBIGUOUS_DESTINATIONThe key is active in more than one account.
422TRANSFER_DESTINATION_NOT_ACTIVEThe destination account is not active.
422TRANSFER_MAIN_ACCOUNT_DESTINATIONThe key belongs to an account that does not receive transfers.
422TRANSFER_NO_ORIGIN_KEYYour account has no active Pix key. See Pix keys.
422ACCOUNT_BLOCKED_BY_PROVIDERThe bank blocked outflows on your account or inflows on the destination account. details.operation says which.
422ACCOUNT_HELD_BY_STAFFYour account or the destination account is held by support.

All rejections, with the code, are in Transfer to another PayZu account, and credential and scope rejections are in Authentication.

On this page