Change Log

September 12th 2025

  • When creating an external digital asset wallet, you no longer need to match the accountHolder.address.countryCode to the country for which you would like to onramp funds. In the past, if you wanted to do a BRL to USDC onramp, you would have to create the external digital asset wallet with the accountHolder.address.countryCode of BR. If you wanted to use the same wallet address to onramp from PHP, you would have to create another external digital asset wallet with accountHolder.address.countryCode of PH. This is no longer the case. One external digital asset wallet can be used for all onramps (and accountHolder.address.countryCode should represent where the user actually lives)
  • External digital asset wallets will start requiring currency codes. This means that if you want to use one address to send USDC and USDT, you will need to create two accounts to represent each currency (with only the currency code differing between the two). Breaking change: Starting October 10th, creating an external digital asset wallet will return an error if no currency code is provided. For all existing wallets that don't have a currency code by that date, these will be automatically updated to have the currency code of their last payment (defaulting to USDC if no payment exists). EDIT: The breaking change date for this has been postponed. We will provide a new breaking change date shortly.
  • For virtual bank accounts with liquidation information, liquidationAccountId can be provided instead of liquidationInformation. To do this, the liquidation account must first be created and its ID can then be used in the liquidationAccountId field for a virtual bank account. Breaking change: Starting October 10th, the liquidationInformation parameter will be deprecated entirely. EDIT: The breaking change date for this has been postponed. We will provide a new breaking change date shortly.
  • When creating a business customer, the estimatedAnnualRevenueUSD field had two incorrect options: FIFTY_MILLION_TO_TWENTY_FIVE_MILLION and OVER_TWENTY_FIVE_MILLION. Two new options have been added to replace these: FIFTY_MILLION_TO_TWO_HUNDRED_AND_FIFTY_MILLION and OVER_TWO_HUNDRED_AND_FIFTY_MILLION. Breaking change: Starting October 10th, the old incorrect fields will be removed
  • You can now include a returnAddress field when creating a stablecoin-to-fiat payment in case you want the funds returned to a specific address in case of payment failure
  • The reference field for payments will replace the memo field. Breaking change: Starting October 10th, the memo field will be deprecated
  • In the GET /accounts/:id and accounts webhook events, the accountHolder object will replace paymentAccountHolder. Currently, both are present. Breaking change: Starting October 10th, only accountHolder will remain

September 8th 2025

Summary: Deposits are being replaced with payments of type PAYIN.

Previously, when funds were sent to a Virtual Account, a new deposit record was created with details regarding the source of the money. However, for Virtual Accounts with automatic conversion to stablecoins or fiat, no data was provided regarding where the money was sent. To fix this issue, we are deprecating the use of deposits entirely and introducing a new paymentType parameter for payments. The types are described below:

  • INTERNAL: Funds are sent from a Virtual Account to another Virtual Account (e.g., from a virtual bank account to a virtual digital asset wallet)
  • PAYIN: Funds are sent from an External Account to a Virtual Account (e.g., from an external bank account to a virtual bank account). This will replace the deposit record
  • PAYOUT: Funds are sent from a Virtual Account to an External Account (e.g., from a virtual bank account to an external digital asset wallet)
  • EXTERNAL: Funds are sent from an External Account to another External Account (e.g., from an external bank account to an external digital asset wallet).

By representing deposits as payments, you will be able to see all money movement in a chronological order and keep track of automatic conversions. Below is a short representation of the change:

Before:

  1. Funds are sent to a virtual account
  2. A deposit record is created and an account.deposit webhook is triggered
  3. No record is created or webhook triggered for the automatic conversion

After:

  1. Funds are sent to a virtual account
  2. A PAYIN payment record is created and a payment.created webhook is triggered
  3. Another payment record is created to represent the automatic conversion (PAYOUT or INTERNAL depending on the destination account type) and a payment.created webhook is triggered

Breaking Change: Deposits will be deprecated fully on September 30th. Before then, your existing deposits will be transferred to your payments as PAYINs.

August 25th 2025

Summary: The main objective of this release is to allow developers to select a specific rail for a payment. Previously, only LOCAL, CRYPTO, and WIRE were allowed. The dashboard has NOT yet been updated to use these new endpoints.

Corridors

The first step was creating a new GET /payments/corridors endpoint that allows you to see which corridors/rails are available. A few notes:

  • "rails" now contains all supported blockchains and all supported fiat rails. "chain" has been deprecated
  • "currencyCodes" and "rails" are now arrays, allowing us to greatly reduce the number of objects returned by the response. Instead of having an object for every permutation of blockchain and stablecoin (e.g., POLYGON/USDC, ETHEREUM/USDC, BASE/USDC), these are all concatenated into one object
  • Instead of having fees for LOCAL, CRYPTO, and WIRE, fees are now specific to the source and destination rail combination.

Breaking Change: GET configurations/corridors will be deprecated September 30th.

Before (GET configurations/corridors):

"data": [
        {
            "source": {
                "currencyCode": "USDC",
                "chain": "POLYGON"
            },
            "destination": {
                "currencyCode": "BRL",
                "chain": null,
                "countryCode": "BR"
            },
            "fees": {
                "LOCAL": {
                    "fixedFeeInUSD": 1,
                    "variableFeeInSourceCurrency": 0.005
                },
                "CRYPTO": {
                    "fixedFeeInUSD": 1,
                    "variableFeeInSourceCurrency": 0.005
                },
                "WIRE": {
                    "fixedFeeInUSD": null,
                    "variableFeeInSourceCurrency": null
                }
            }
        }
]

After (GET payments/corridors):

"data": [
        {
            "source": {
                "currencyCodes": [
                    "USDC",
                    "USDT",
                    "EURC"
                ],
                "rails": [
                    "POLYGON",
                    "ETHEREUM",
                    "BASE",
                    "SOLANA",
                    "TRON"
                ]
            },
            "destination": {
                "currencyCodes": [
                    "BRL"
                ],
                "rails": [
                    "TED"
                ],
                "countryCode": "BR"
            },
            "fees": {
                "fixed": 1,
                "variable": 0.005
            }
        }
]

Customers

Previously, if your team had USD corridors enabled, all onboarded customers would require passing through compliance checks mandated by US banks (which tend to be slower). Since it's possible not all your customers need access to USD corridors, you can now specify which capabilities you would like to enable for the onboarded customer: USD and OTHER (putting both or not including the capabilities parameter in the request will give the customer access to all corridors).

Important note: USD capability also covers EUR onramps.

Example

{
    "type": "INDIVIDUAL",
    "capabilities": ["OTHER"], // Optional
    "firstName": "Jason",
    "lastName": "Smith",
    ....
}

Accounts

GET /requirements

With the ability to choose which rail to send the payment, it is also important to know which parameters are required for each rail when creating an account. We have therefore created a new GET /accounts/requirements endpoint to replace the existing GET /accounts/requirements/external-bank-accounts endpoint. A few notes:

  • Objects are not grouped by INDIVIDUAL and BUSINESS anymore. Instead, each variable will have an accountHolderTypes array to designate to whom it applies
  • The regex provided for IBANs is now specific to the country

Breaking Change: GET /accounts/requirements/external-bank-accounts will be deprecated September 30th.

Before (GET /accounts/requirements/external-bank-accounts):

{
    "INDIVIDUAL": [
        {
            "variableName": "bank.accountNumber",
            "regex": "^[0-9]{2,15}$",
            "example": "001000456789",
            "enum": []
        },
        ....
    ],
    "BUSINESS": [
        {
            "variableName": "bank.accountNumber",
            "regex": "^[0-9]{2,15}$",
            "example": "001000456789",
            "enum": []
        },
        ...
    ]
}

After (GET /accounts/requirements):

[
    {
        "variableName": "bank.accountNumber",
        "rails": [
            "TED"
        ],
        "accountHolderTypes": [
            "INDIVIDUAL",
            "BUSINESS"
        ],
        "regex": "^[0-9]{2,15}$",
        "example": "001000456789",
        "enum": []
    },
    ...
    {
        "variableName": "bank.pixCode",
        "rails": [
            "PIX"
        ],
        "accountHolderTypes": [
            "INDIVIDUAL",
            "BUSINESS"
        ],
        "regex": "^.{1,100}$",
        "example": "01.234.456/5432-10 (cnpj) | 123.456.789-87 (cpf) | +5511912345678",
        "enum": []
    }
]

POST /accounts

In addition to the revamped requirements endpoint, you can now specify the rails you want to unlock for the account you create. Currently, this can be done for two different countries:

  • US: ACH, ACH_SAME_DAY, and DOMESTIC WIRE. Some US banks don't have all three rails for their accounts, so it's important to designate which rails can be used to send funds. If you do not include the "rails" field in the request, the account will get access to all 3 by default
  • BR: TED and PIX. These rails have different field requirements. By selecting which rail you want to enable for the account, you can make sure to only ask your customer for the actual fields required for the rail you want to use. If you do not include the "rails" field in the request, the account will get access to TED by default

Example

{
    "type": "EXTERNAL_BANK_ACCOUNT",
    "currencyCode": "BRL",
    "isThirdParty": true,
    "rails": ["TED", "PIX"],
    "bank": {
        "name": "BANCO DO BRASIL S.A",
    ....
}

Payments

GET /rate

With the introduction of specific rails, the GET /payments/rate/walapay has also been replaced with GET /payments/rate to provide the details of the specific rail chosen. A few notes:

  • Instead of query parameters sourceChain and destinationChain, it is sourceRail and destinationRail
  • You can still use LOCAL as the value for "rail". Behind the scenes, we map this value to the appropriate country-specific rail
  • The current /payments/rate/walapay will append the results of the new endpoint automatically so that you can transition from one to the other smoothly

Breaking Change: GET /payments/rate/walapay will be deprecated September 30th.

Before (GET /payments/rate/walapay):

{
    "midMarketRate": 5.4952529294,
    "invertedMidMarketRate": 0.1819755763,
    "walapayFees": {
        "description": "The Walapay fees for the payment based on the destination rail",
        "LOCAL": {
            "fixed": 1,
            "variable": 0.001
        },
        "WIRE": {
            "fixed": 45,
            "variable": 0.0065
        },
        "CRYPTO": {
            "fixed": null,
            "variable": null
        }
    },
    "walapayRates": {
        "description": "[Deprecated] The mid market rate minus the Walapay variable fee: mid market rate * (1 - Walapay variable fee)",
        "LOCAL": 5.4897576764706,
        "WIRE": 5.459533785358901,
        "CRYPTO": null
    },
    "developerFees": {
        "description": "The developer fees for the payment. Added on top of the walapay fees.",
        "fixed": null,
        "variable": null
    },
    "calculatedAmounts": {
        "LOCAL": {
            "sourceAmount": 679.27,
            "destinationAmount": 3723.52,
            "totalFeeInSourceCurrency": 1.68
        },
        "WIRE": {
            "sourceAmount": 727.03,
            "destinationAmount": 3723.52,
            "totalFeeInSourceCurrency": 49.44
        },
        "CRYPTO": {
            "sourceAmount": null,
            "destinationAmount": null,
            "totalFeeInSourceCurrency": null
        }
    },
    "quoteId": "cmdxts4kx0000dunid010mqd7"
}

After (GET /payments/rate):

{
    "midMarketRate": 5.495449870300001,
    "invertedMidMarketRate": 0.18196857419999998,
    "walapayFee": {
        "fixed": 1,
        "variable": 0.007
    },
    "developerFee": {
        "fixed": 1,
        "variable": 0.01
    },
    "calculatedAmount": {
        "description": "The calculated amounts for the payment, along with total fee (both walapay and developer).destinationAmount = (((sourceAmount - walapayFixedFee) * (1 - walapayVariableFee)) - developerFixedFee) * midMarketRate * (1 - developerVariableFee). sourceAmount = (destinationAmount / (midMarketRate * (1 - developerVariableFee)) + developerFixedFee) / (1 - walapayVariableFee) + walapayFixedFee.",
        "sourceAmount": 691.24,
        "destinationAmount": 3723.52,
        "totalFeeInSourceCurrency": 13.68
    },
    "quoteId": "cmdxtdy6e000010y04ovhu28i"
}

POST /payments

In addition to the new rate endpoint, the POST /payments endpoint has been updated to allow you to choose a country-specific rail if desired. A few notes:

  • To start, the currencies that will have multiple options:
    • USD: ACH, ACH_SAME_DAY, DOMESTIC_WIRE
    • BRL: TED, PIX
    • INR: IMPS, IMPS_WITH_FIRC
  • You can still use LOCAL if you want Walapay to choose the default option on your behalf
  • You can still use CRYPTO in the request if the rail is a blockchain. We will map the value provided for chain to replace CRYPTO under the hood. The mapped value will be saved as the rail
  • For SWIFT payments, you should use the rail type "SWIFT" (WIRE is deprecated)

Before:

{
    "source": {
        "currencyCode": "USDC",
        "amount": 10,
        "rail": "CRYPTO",
        "chain": "POLYGON",
        "fromAddress": "0x9dE08B842B9f9E27c478fF816330118939a7BF90"
    },
    "destination": {
        "currencyCode": "USD",
        "rail": "LOCAL",
        "accountId": "cmca02pfl000cfc5qjs3emffc"
    },
    "comment": "Test Transaction",
    "memo": "Test",
    "paymentReason": "CONSULTING_FEES",
    "documentReference": "Test"
}

After (with "source.rail" as "POLYGON" and "destination.rail" as "ACH_SAME_DAY"):

{
    "source": {
        "currencyCode": "USDC",
        "amount": 10,
        "rail": "POLYGON",
        "fromAddress": "0x9dE08B842B9f9E27c478fF816330118939a7BF90"
    },
    "destination": {
        "currencyCode": "USD",
        "rail": "ACH_SAME_DAY",
        "accountId": "cmca02pfl000cfc5qjs3emffc"
    },
    "comment": "Test Transaction",
    "memo": "Test",
    "paymentReason": "CONSULTING_FEES",
    "documentReference": "Test"
}