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 theaccountHolder.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 withaccountHolder.address.countryCode
of PH. This is no longer the case. One external digital asset wallet can be used for all onramps (andaccountHolder.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 ofliquidationInformation
. To do this, the liquidation account must first be created and its ID can then be used in theliquidationAccountId
field for a virtual bank account. Breaking change: Starting October 10th, theliquidationInformation
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
andOVER_TWENTY_FIVE_MILLION
. Two new options have been added to replace these:FIFTY_MILLION_TO_TWO_HUNDRED_AND_FIFTY_MILLION
andOVER_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 thememo
field. Breaking change: Starting October 10th, thememo
field will be deprecated - In the GET /accounts/:id and accounts webhook events, the
accountHolder
object will replacepaymentAccountHolder
. Currently, both are present. Breaking change: Starting October 10th, onlyaccountHolder
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:
- Funds are sent to a virtual account
- A deposit record is created and an account.deposit webhook is triggered
- No record is created or webhook triggered for the automatic conversion
After:
- Funds are sent to a virtual account
- A PAYIN payment record is created and a payment.created webhook is triggered
- 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"
}
Updated 3 days ago