Internal Transfers Guide
This guide explains how to perform internal transfers between accounts at the same bank, covering the three available methods and their use cases.
Overview
Internal transfers move funds between accounts within the same bank, without going through the PIX network. They are processed instantly.
The outbound amount respects the same daily ceiling as the mobile app (São Paulo calendar day, 00:00–23:59) and the per-operation PIX cap. If no ceiling is on file, the transfer proceeds, same as the app. A retry with the same Idempotency-Key does not check the limit again.
There are three ways to identify the destination account:
1. Transfer by Account ID
The most performant method. Use when both source and destination accounts are registered in the API.
Endpoint: POST /v1/accounts/{accountId}/transfers/internal
Fields:
Response (200):
When the transfer is refused, the same body comes back with 422,
status: "FAILED" and the errorCode/errorReason fields filled in.
2. Transfer by Document (CPF/CNPJ)
Use when the destination account is not registered in the API. Works for any account in the banking ecosystem.
Endpoint: POST /v1/accounts/{accountId}/transfers/internal/by-document
Fields:
When the destination document has more than one active account,
addressing by document is ambiguous and the request is rejected with
409 multiple_destination_accounts. In that case, use
/transfers/internal/by-bank-account (branch + account number) or
/transfers/internal (destinationAccountId).
3. Transfer by Branch and Account Number
Use when you have the destination’s branch and account number. Works for any account at the bank, registered in the API or not: the settlement bank resolves the holder from holderDocument.
Endpoint: POST /v1/accounts/{accountId}/transfers/internal/by-bank-account
Fields:
Which method to choose?
Practical examples:
- Moving funds between your company’s branches →
/transfers/internal(by Account ID) - Paying a supplier at the bank →
/transfers/internal/by-document(by CPF/CNPJ) - Transferring to a known account by branch number →
/transfers/internal/by-bank-account
Webhooks
After an internal transfer, both parties receive a webhook:
- Sender:
transfer.internal.out - Receiver:
transfer.internal.in
Since v2.61.0, data.source and data.destination identify both parties
with name and tax ID — before that the data block was flat and carried only
sourceAccountId / destinationAccountId, which left the receiving side
unable to tell who sent the money. The flat fields are still there.
When the destination is an account at another bank, destination carries
name and taxId (the ones you supplied in the request), without
accountId/tenantId. The transfer.internal.in leg carries the same payload
with the destination accountId and direction: "IN".
Because settlement is atomic and synchronous, the webhook always arrives with data.status: "SUCCESS" — it is the only possible value for transfer.internal.*. Failures (insufficient funds, non-existent destination account, blocked account, etc.) are signalled in the HTTP response of the POST /transfers/internal* call itself and never produce a webhook.
The webhook sent to the payer (transfer.internal.out) carries the same identifier and description you sent in the request body (POST /transfers/internal*), together with the paymentId and transactionId that are also in the synchronous response. That removes the need for extra statement lookups to close the transfer on your side.
The transfer.internal.in leg is only emitted when the destination account is also managed at CorpX; it repeats the same fields (including the identifier set by the payer), changing only accountId and direction.
To receive these webhooks, include transfer.internal.in and/or transfer.internal.out in your subscription event types.
Common Errors
422 is an outcome, 202 is a doubt
A refused transfer answers 422 with status: "FAILED", errorCode and
errorReason — the money did not move and no webhook is coming. A 202
with status: "PENDING" means something else: the call went out and
confirmation did not arrive within the synchronous window. In that case check the
statement before re-issuing — repeating the POST with the same
Idempotency-Key reuses the in-flight operation, but a fresh key may transfer
twice.