Internal Transactions move funds book-to-book between two Monato accounts (same institution). These do not go through SPEI, so there’s no CEP and settlement is typically near-real-time when both instruments are active and funded.
Rail: Internal (book-to-book)
CEP: Not applicable
Latency: Near-real-time
Use cases:
- Move funds between Cost Centers / Business Units
- Intra-merchant payouts
- Reserve account management
- Move funds to another Monato customer
Webhooks / Notifications:
- The credit leg of every successful internal transaction generates a
MONEY_INwebhook towards the client that owns the destination instrument (if they have aMONEY_INwebhook registered). - The payload format matches the Money In confirmation guide, with
sub_category = "INT_CREDIT".
- The credit leg of every successful internal transaction generates a
Parity with Money Out: Request body format and most validations are the same as Money Out, with a few extra internal checks noted below.
POST /v1/transactions/internal_transaction
Path params: none
Query params: none
Headers
Authorization: Bearer <JWT>Content-Type: application/json
Body
{
"client_id": "c2d1d1e3-3340-4170-980e-e9269bbbc551",
"source_instrument_id": "709448c3-7cbf-454d-a87e-feb23801269a",
"destination_instrument_id": "dd7f8d89-94dd-43ca-871b-720fde378b52",
"transaction_request": {
"amount": "1.90",
"currency": "MXN",
"description": "Internal transfer",
"external_reference": "1238766"
}
}Field notes
source_instrument_id,destination_instrument_idIDs of two valid internal instruments. They can belong either to your client or to one of your customers, as long as both instruments are internal to Monato.
To discover internal instruments available for your client or customers, use:
GET /v1/clients/{clientId}/instrumentswith the optional customer_id filter.
- amount
- Must be a numeric string with 2 decimal places (e.g.,
"1.90"). - Must be greater than 0 (otherwise backend returns
DATA_ERROR: "Transaction Amount must be higher than 0.").
- Must be a numeric string with 2 decimal places (e.g.,
- currency
- Only
"MXN"is supported (otherwise:DATA_ERROR: "Transaction currency unsupported.").
- Only
- description
- Length < 40 characters (if exceeded:
DATA_ERROR: "Transaction description must have less than 40 characters length.").
- Length < 40 characters (if exceeded:
- external_reference
- Must be numeric and max 7 digits (otherwise:
DATA_ERROR: "External reference should be numeric and have a maximum length of 7 digits.").
- Must be numeric and max 7 digits (otherwise:
- IDs
client_id,source_instrument_id,destination_instrument_idmust be valid UUIDs.
- Source
- Exists, is active, not blocked, and has sufficient funds
(if not:FAILED_PRECONDITION: "The account does not have sufficient funds.").
- Exists, is active, not blocked, and has sufficient funds
- Destination
- Exists / is assigned, active, and not blocked
(if not:FAILED_PRECONDITION: "The account is not currently active.").
- Exists / is assigned, active, and not blocked
- Same institution (internal rail)
- Destination must be internal to Monato (if not:
409 external_transfer_not_allowed).
- Destination must be internal to Monato (if not:
- Different instruments
- Best practice:
source_instrument_id≠destination_instrument_id.
- Best practice:
{
"id": "09c9caac-3b74-4690-8ac5-5a01b2559b3f",
"bankId": "d3435bd9-998d-4e8a-9067-6b71d5fd3ac7",
"clientId": "b000654b-4d12-46e5-b451-662459b6effc",
"externalReference": "1238801",
"trackingId": "20250925FINCHCUCHFGMRLZ",
"description": "Prueba Internal 25/09/25",
"amount": "1.00",
"currency": "MXN",
"category": "INTER_TRANS",
"subCategory": "INT_DEBIT",
"transactionStatus": "LIQUIDATED",
"audit": {
"createdAt": "2025-09-25 15:48:28.486316-06:00",
"updatedAt": "2025-09-25 15:48:28.773897-06:00",
"deletedAt": "None",
"blockedAt": "None"
}
}{
"code": 9,
"message": "API Error",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "FAILED_PRECONDITION",
"domain": "CORE",
"metadata": {
"error_detail": "The account is not currently active.",
"http_code": "400",
"module": "Transactions",
"method_name": "InternalTransaction",
"error_code": "10-E4120"
}
}
]
}{
"code": 9,
"message": "API Error",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "FAILED_PRECONDITION",
"domain": "CORE",
"metadata": {
"error_detail": "The account does not have sufficient funds.",
"http_code": "400",
"module": "Transactions",
"method_name": "InternalTransaction",
"error_code": "10-E4120"
}
}
]
}{
"code": 9,
"message": "API Error",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "DATA_ERROR",
"domain": "CORE",
"metadata": {
"error_detail": "External reference should be numeric and have a maximum length of 7 digits.",
"http_code": "400",
"module": "Transactions",
"method_name": "InternalTransaction",
"error_code": "10-E4120"
}
}
]
}{
"code": 9,
"message": "API Error",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "DATA_ERROR",
"domain": "CORE",
"metadata": {
"error_detail": "Transaction Amount must be higher than 0.",
"http_code": "400",
"module": "Transactions",
"method_name": "InternalTransaction",
"error_code": "10-E4120"
}
}
]
}{
"code": 9,
"message": "API Error",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "DATA_ERROR",
"domain": "CORE",
"metadata": {
"error_detail": "Transaction description must have less than 40 characters length.",
"http_code": "400",
"module": "Transactions",
"method_name": "InternalTransaction",
"error_code": "10-E4120"
}
}
]
}{
"code": 9,
"message": "API Error",
"details": [
{
"@type": "type.googleapis.com/google.rpc.ErrorInfo",
"reason": "DATA_ERROR",
"domain": "CORE",
"metadata": {
"error_detail": "Transaction currency unsupported.",
"http_code": "400",
"module": "Transactions",
"method_name": "InternalTransaction",
"error_code": "10-E4120"
}
}
]
}When an internal transaction is successfully processed:
- The
POST /v1/transactions/internal_transactionresponse returns the debit leg (source side). - Additionally, the client that owns the destination instrument receives a
MONEY_INwebhook.
The webhook:
Uses the common
MONEY_INenvelope:{ "id_msg": "6daea2d2-ccb0-48f3-917c-f387dc8e99b0", "msg_name": "MONEY_IN", "msg_date": "2025-11-20", "body": { "...": "..." } }Includes, in the
body, fields like:{ "id": "a0037594-5064-4dda-896b-f9b5dd4988dd", "beneficiary_account": "734185000000001177", "beneficiary_name": "MERCHANT TEST", "beneficiary_rfc": "FTR230125Q00", "payer_account": "734185000000000822", "payer_name": "Customer Test-1 Legal", "payer_rfc": "ND", "payer_institution": "90734", "amount": "1.00", "transaction_date": "2025-11-20 15:05:59", "tracking_key": "20251120FINCHESDHI7FVTU", "payment_concept": "CUST - SPEI", "numeric_reference": "1100003", "sub_category": "INT_CREDIT", "registered_at": "2025-11-20T15:05:59.915184-06:00", "owner_id": "24f1e5d5-4045-4b1a-a0c4-5e6c6b1d44ef" }
See the Money In confirmation documentation for the full field-by-field description of the
MONEY_INwebhook payload.
For internal credits, the webhook is informational:
- The funds are already moved book-to-book when the webhook is sent.
- HTTP status codes on your response do not trigger an automatic rollback or refund.
| Failing rule | Typical response |
|---|---|
| Source account inactive/blocked | 400 FAILED_PRECONDITION (error_code: 10-E4120) |
| Insufficient funds (source) | 400 FAILED_PRECONDITION (error_code: 10-E4120) |
external_reference non-numeric or > 7 digits | 400 DATA_ERROR (error_code: 10-E4120) |
amount ≤ 0 | 400 DATA_ERROR (error_code: 10-E4120) |
description ≥ 40 characters | 400 DATA_ERROR (error_code: 10-E4120) |
currency other than MXN | 400 DATA_ERROR (error_code: 10-E4120) |
| Destination not found / not assigned | 404 destination_not_found |
| Destination is external (not internal rail) | 409 external_transfer_not_allowed |
Debit leg (source account):
category = "INTER_TRANS"subCategory = "INT_DEBIT"
Credit leg (destination account, as seen in the MONEY_IN webhook or related transaction):
category = "INTER_TRANS"subCategory = "INT_CREDIT"
⚠️ Disclaimer
We plan to unify the APIs in the future so that both external Money Out (SPEI) and internal transactions (between Monato accounts) are supported through a single endpoint.
For now, please use the endpoints separately as documented:
- Use
POST /v1/transactions/internal_transactionfor internal (book-to-book) movements. - Use the Money Out endpoints for external SPEI transfers.
- There is no
/refundendpoint for internal transactions. If you need to “reverse” an internal movement, you must create a newinternal_transactionin the opposite direction.