Send transfers

Initiate real-time payments using our outbound Transfers API

To create outbound transfers with Fintoc's Transfers API, complete the setup guide, then follow these steps:

  1. Configure JSON Web Signature (JWS) signing keys and generate a JWS signature.
  2. Add funds to the account's root_account_number.
  3. From your backend, create a transfer using your Secret Key and a JWS signature.
  4. Monitor transfer status.

The following diagram shows how Fintoc interacts with you and the counterparty receiving the payout.

Step 1: Configure JWS signing keys and generate a JWS signature

Every request to the Fintoc Transfers endpoints requires a JWS signature. JWS is a standard mechanism used to digitally sign a piece of data to ensure its integrity and authenticity.

To learn how to sign an API call, follow the steps in the JWS Signature guide.

Step 2: Add funds to Fintoc

To create transfers, fund your Account first by adding funds to the root_account_number. The deposit appears as an inbound transfer.

Step 3: Create a transfer

After you add funds to your account, create a transfer from your backend. Include your test Secret Key, JWS signature, origin account, amount, currency, and counterparty.

The examples below show successful transfer responses. To test unsuccessful results, use the terminal-status table in the Test the integration section below.

Use an idempotency key

Fintoc supports idempotency to safely retry transfers without creating a duplicate transfer. When creating a transfer, use an idempotency key. If a connection error occurs, repeat the request safely.

To perform an idempotent request, provide an Idempotency-Key header to the request. Learn more about idempotent request in this guide.

Create a transfer for Mexico 🇲🇽

Here is an example that creates a transfer of $590.13 MXN. The amount field is in the smallest currency unit, so $590.13 is the integer 59013.

curl --request POST \
     --url https://api.fintoc.com/v2/transfers \
     --header 'Authorization: sk_test_9c8d8CeyBTx1VcJzuDgpm4H' \
     --header 'Fintoc-JWS-Signature: CNMaYaDGU3ZhFV1ve6p3sAdYXhEklej8DVIAMqIWCkpNmT6Jp7iigcndXwH5q3WQFHiswgIQU5-_-4rV3jKGptCROmEyWPW8_elhYH1apzAyjOjyZ55ygv37xKHzIFhixzAwmXlAv4pfD4lVelYWVNOSN7REA0QJeCy2vKdqZ5cjqCXQ1lkQUlzOE7dpuNoAkhAhAJJ8HaamFKy7Gl7uwmqbIr-dVYv21d_9O7mO26n0gy3zWXD2nJDxU5Mzl2pZd8-sFvUr9Kmp_YkeRMh4bSe0fr1Uc_YgkjpmYUyu7kaxRWTbAdJ3GwqWFMUDiyfhHdzvZPZyU4VkWreimoydMA' \
     --header 'Idempotency-Key: 1ebfd86c-a75b-4606-872f-9f1cdd9724ca' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "amount": 59013,
  "currency": "mxn",
  "account_id": "acc_M8sKf230BgHjD4",
  "comment": "Pago de credito 10451",
  "reference_id": "150195",
  "counterparty": {
    "account_number": "000000000000000000"
  },
  "metadata": {
  	"customer_id": "12050123"
   }
}
'
const transfer = await fintoc.v2.transfers.create({
  idempotency_key: '1ebfd86c-a75b-4606-872f-9f1cdd9724ca',
  amount: 59013,
  currency: 'mxn',
  account_id: 'acc_M8sKf230BgHjD4',
  comment: 'Pago de credito 10451',
  reference_id: '150195',
  counterparty: { account_number: '000000000000000000' },
  metadata: { customer_id: '12050123' }
});
transfer = client.v2.transfers.create(
  idempotency_key="1ebfd86c-a75b-4606-872f-9f1cdd9724ca",
  amount=59013,
  currency="mxn",
  account_id="acc_M8sKf230BgHjD4",
  comment="Pago de credito 10451",
  reference_id="150195",
  counterparty={"account_number": "000000000000000000"},
  metadata={"customer_id": "12050123"}
)

A successful request response looks like this:

{
  "object": "transfer", 
  "id": "tr_jKaHD105H",
  "amount": 59013,  
  "currency": "mxn", 
  "direction": "outbound",
  "status": "succeeded",
  "transaction_date": "2020-04-17T05:12:41.462Z",
  "post_date": "2020-04-17T00:00:00.000Z",
  "comment": "Pago de credito 10451",
  "reference_id": "150195",
  "tracking_key": "s2123423423324334",
  "receipt_url": "https://www.banxico.org.mx/cep/",
  "mode": "test",
  "return_reason": null,
  "counterparty": {
    "holder_id": "AAA010101AAA",
    "holder_name": "Test Customer 1",
    "account_number": "000000000000000000",
    "account_type": "clabe",  
    "institution": {
      "id": "mx_bbva_mexico",
      "name": "BBVA Mexico",
      "country": "mx" 
     }
   },
  "account_number": {
    "id": "acno_Kasf91034gj1AD",
    "account_id": "acc_Jas92lf9adg94ka",
    "number": "111111111111111111",
    "created_at": "2024-03-01T20:09:42.949787176Z",
    "mode": "test",
    "description": null,
    "metadata": {},
    "object": "account_number"
  },
  "metadata": {
    "customer_id": "12050123"
  }
}
🚧

Currencies are represented as integers

For example, Fintoc represents MXN $10.29 as 1029.

You can read more about currencies here.

📘

Remember to add the institution ID

When transferring to a standardized Mexican bank account number (CLABE), no counterparty institution ID is necessary. Fintoc deduces the institution from the CLABE number. If your counterparty is a mobile phone number or debit card, include the institution ID as a 5-digit number, as shown here. Otherwise, you get a 400 Bad Request error.

In Mexico, the Counterparty object requires one attribute:

ParameterDescription
account_numberThe recipient's bank account number (🇲🇽 CLABE, mobile number, or debit card number).

Create a transfer for Chile 🇨🇱

Here is an example that creates a transfer of $1,869 CLP. Because CLP has no minor unit, the amount field is the same integer, 1869.

curl --request POST \
     --url https://api.fintoc.com/v2/transfers \
     --header 'Authorization: sk_test_9c8d8CeyBTx1VcJzuDgpm4H' \
     --header 'Fintoc-JWS-Signature: CNMaYaDGU3ZhFV1ve6p3sAdYXhEklej8DVIAMqIWCkpNmT6Jp7iigcndXwH5q3WQFHiswgIQU5-_-4rV3jKGptCROmEyWPW8_elhYH1apzAyjOjyZ55ygv37xKHzIFhixzAwmXlAv4pfD4lVelYWVNOSN7REA0QJeCy2vKdqZ5cjqCXQ1lkQUlzOE7dpuNoAkhAhAJJ8HaamFKy7Gl7uwmqbIr-dVYv21d_9O7mO26n0gy3zWXD2nJDxU5Mzl2pZd8-sFvUr9Kmp_YkeRMh4bSe0fr1Uc_YgkjpmYUyu7kaxRWTbAdJ3GwqWFMUDiyfhHdzvZPZyU4VkWreimoydMA' \
     --header 'Idempotency-Key: 1ebfd86c-a75b-4606-872f-9f1cdd9724ca' \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "amount": 1869,
  "currency": "clp",
  "account_id": "acc_M8sKf230BgHjD4",
  "comment": "Pago de credito 10451",
  "counterparty": {
    "holder_id": "11.111.111-1",
    "holder_name": "Test Customer 1",
    "account_number": "000000000",
    "account_type": "checking_account",
    "institution_id": "cl_banco_de_chile"
  },
  "metadata": {
  	"customer_id": "12050123"
  }
}
'
const transfer = await fintoc.v2.transfers.create({
  idempotency_key: '1ebfd86c-a75b-4606-872f-9f1cdd9724ca',
  amount: 1869,
  currency: 'clp',
  account_id: 'acc_M8sKf230BgHjD4',
  comment: 'Pago de credito 10451',
  counterparty: {
    holder_id: '11.111.111-1',
    holder_name: 'Test Customer 1',
    account_number: '000000000',
    account_type: 'checking_account',
    institution_id: 'cl_banco_de_chile'
  },
  metadata: { customer_id: '12050123' }
});
transfer = client.v2.transfers.create(
  idempotency_key="1ebfd86c-a75b-4606-872f-9f1cdd9724ca",
  amount=1869,
  currency="clp",
  account_id="acc_M8sKf230BgHjD4",
  comment="Pago de credito 10451",
  counterparty={
    "holder_id": "11.111.111-1",
    "holder_name": "Test Customer 1",
    "account_number": "000000000",
    "account_type": "checking_account",
    "institution_id": "cl_banco_de_chile"
  },
  metadata={"customer_id": "12050123"}
)

A successful request response looks like this:

{
  "object": "transfer",
  "id": "tr_jKaHD105H",
  "amount": 1869,   
  "currency": "clp", 
  "direction": "outbound",
  "status": "succeeded",
  "transaction_date": "2020-04-17T05:12:41.462Z",
  "post_date": "2020-04-17T00:00:00.000Z", 
  "comment": "Pago de credito 10451",
  "reference_id": null,
  "receipt_url": null,
  "tracking_key": null,
  "mode": "test",
  "return_reason": null,
  "counterparty": {
    "holder_id": "11.111.111-1",
    "holder_name": "Test Customer 1",
    "account_number": "000000000",
    "account_type": "checking_account", 
    "institution": {
      "id": "cl_banco_de_chile",
      "name": "Banco de Chile",
      "country": "cl" 
     }
   },
  "account_number": {
    "id": "acno_Kasf91034gj1AD",
    "account_id": "acc_Jas92lf9adg94ka",
    "number": "111111111111111111",
    "created_at": "2024-03-01T20:09:42.949787176Z",
    "mode": "test",
    "object": "account_number",
    "description": null,
    "metadata": {}
  },
  "metadata": {
    "customer_id": "12050123"
  }
}

In Chile, the Counterparty object requires five attributes:

ParameterDescription
holder_idThe account holder's Chilean tax ID (RUT).
holder_nameThe account holder's full name.
account_numberThe recipient's bank account number at the counterparty institution.
account_typeType of account. Supported types are checking_account and sight_account.
institution_idFintoc institution ID for the bank receiving the transfer. You can see the code for each institution here.

Step 4: Monitor transfer status

Transfer status flow

A transfer's status is one of pending, succeeded, failed, returned, return_pending, or rejected. For more details on transfer statuses, see the Transfers data model.

Monitor status using webhooks

Fintoc sends a transfer.outbound.succeeded event when the transfer settles. Use the webhook guide to receive these events and run actions, such as sending a notification email to your customer or logging the transfer in your ERP.

We recommend handling the following events:

EventDescription
transfer.outbound.succeededSent when the transfer is sent successfully.
transfer.outbound.rejected

🇲🇽 In Mexico, Fintoc sends this event when Banco de Mexico or the counterparty institution rejects the transfer. See the rejection causes.

🇨🇱 In Chile: Sent when the counterparty institution has rejected the transfer.

rejected is a final state and undergoes no further changes.

transfer.outbound.failedSent when the transfer has not been able to reach its destination account, due to an error during the process.
❗️

Webhooks may arrive in disorder

For example: a transfer.outbound.rejected webhook event could arrive before the same transfer's transfer.outbound.succeeded webhook, even though the transfer first succeeded and then was rejected.

Test the integration

Use your test Secret Key (sk_test_...) to run transfers in test mode without moving real money. Every response includes "mode": "test".

In test mode, a transfer reaches the same terminal statuses as in production. The outcome is not forced by a predefined input. It depends on how the receiving party resolves the transfer. Use the three terminal statuses to confirm your webhook handling:

OutcomeResulting statusWebhook event
Succeededsucceededtransfer.outbound.succeeded
Rejectedrejectedtransfer.outbound.rejected
Failedfailedtransfer.outbound.failed