Step 3: Payments

Available Corridors

To see which corridors have been activated for your team, use the GET corridors endpoint or see the team section of the Walapay dashboard.

When opening a new corridor, it is common for the first few payments to be checked by our banking partner's compliance team, which may delay these payments momentarily. This security audit is relaxed once these initial payments are validated and processed.

Types

There are 5 types of payments:

TypeDefinition
PAYINFunds are sent from an External Account to a Virtual Account (e.g., from an external bank account to a virtual bank account). Think of this as a deposit.
PAYOUTFunds are sent from a Virtual Account to an External Account (e.g., from a virtual bank account to an external digital asset wallet).
INTERNALFunds are sent from a Virtual Account to another Virtual Account (e.g., from a virtual bank account to a virtual digital asset wallet).
EXTERNALFunds are sent from an External Account to another External Account (e.g., from an external bank account to an external digital asset wallet).
REFUNDFunds are being sent back in accordance to the refund logic described in the documentation below.

Statuses

Payments of type PAYOUT, EXTERNAL, and INTERNAL can have the following statuses:

Two important notes:

  1. Completed status: If the payment is to a bank account, we do not have visibility passed the receiving bank accepting the funds. If a customer cannot see their funds for a completed payment, they should contact their bank to understand why the funds have not yet been credited to their account.
  2. Expired status: All payments created without a source.accountId in the payload will start with the AWAITING_FUNDS status. If the payment is not funded within 3 days of creation, it will be marked as EXPIRED (and any funds sent to it will expire). This is done for general housekeeping (so very old, unused payments do not stay open forever).

Funding

There are two ways payments can be funded. For detailed request/response examples of these two types of funding, visit the subpages (Fiat-to-Fiat, Fiat-to-Stablecoin, etc.).

With Source Account

The payment can include a source.accountId parameter in the payload that must be a virtual bank account or a virtual digital asset wallet. The starting status of a payment is PENDING, and the funds will be automatically pulled from this account. This scenario is best if you do not have your own banking or wallet provider.

Without Source Account

If the payment does not include a source.accountId, then the payment will have a starting status of AWAITING_FUNDS and the funds must be sent by the customer to the provided bank account or wallet address provided in the funding instructions object of the response. Note that payments created using this method will only be active for a short period of time (see quotes section below for more details). Exact cut offs can be found in the subpages (Fiat-to-Fiat, Fiat-to-Stablecoin, etc.)

FX

Source / Destination

By default, a payment guarantees a specific source and destination amount upon creation:

  1. If you create a payment with 100 USDC as the source.amount / source.currencyCode and PHP as the destination.currencyCode, the POST /payments response will include the destination.amount of PHP the beneficiary will receive: 5838
  2. If you instead create the payment with 5838 PHP as the destination.amount / destination.currencyCode, then the response will include 100 USDC as the source.amount and source.currencyCode

This allows you to know exactly how much will be required to send the payment and how much will arrive to the recipient. A limitation, however, is that you won't know what the source / destination amount pair will be until after you've created the payment. The solution is to create a quotes beforehand...

Quotes

If you need to be able to show the source and destination amount (and the exchange rate between the two currencies) to your user before the payment request is actually sent (e.g., they're on your app UI and want to know how much PHP they'll get if they send 100 USDC), include the createQuote parameter in the rate endpoint to receive a quoteId. Once a quote is created, you have 1 minute to include it in a POST /payments request (and it cannot be used for more than 1 payment).

📘

Quotes cannot be used for payments that have a source currency of MXN.

Developer Fees

📘

Developer Fees are only active for Stablecoin-to-Fiat and Stablecoin-to-Stablecoin payments.

To add your own fees on payments, you can use the developerFee object within the Payment payload. These fees will accumulate and can be sent to your account of choice (contact Walapay support to set this up) on a monthly basis.

This fee can be combination of a fixed amount and a variable percentage. Note that the fixed fee is always deducted before the variable fee is applied. Example: $1 Fixed with 0.01 variable fee will result in: (source.amount - 1) * (1 - 0.01), where the source.amount already includes the Walapay fees.

The fee is denominated in the source currency if that is a fiat currency, or in the fiat currency to which the source currency is pegged. If you are using a quote, the developer fee will be set to the quote's developer fee; you cannot set this field if you are using a quote.

Errors

When a payment fails, an error code will be provided in the payment object.

Error CodeDefinition
FUNDING_RECEIVED_AFTER_QUOTE_EXPIREDFunds were received after the FX quote expired, so the rate is no longer valid and the transfer cannot be processed.
AMOUNT_TOO_LOW_FOR_CURRENCY_PAIR_AND_RAILThe transfer amount is below the minimum allowed for the specified currency pair and payment rail.
AMOUNT_TOO_HIGH_FOR_CURRENCY_PAIR_AND_RAILThe transfer amount exceeds the maximum allowed for the specified currency pair and payment rail.
INVALID_EXTERNAL_ACCOUNT_DETAILSThe provided beneficiary account details are invalid. Therefore, the beneficiary bank could not credit the funds. This error is given if the bank does not specify what exactly is incorrect.
INVALID_EXTERNAL_ACCOUNT_NUMBERThe beneficiary account number is invalid or incorrectly formatted.
INVALID_EXTERNAL_ACCOUNT_HOLDER_NAMEThe beneficiary account holder name is invalid, missing, or does not meet validation requirements.
INVALID_EXTERNAL_ACCOUNT_HOLDER_ADDRESSThe beneficiary account holder address is invalid, incomplete, or improperly formatted.
FUNDS_RETURNED_BY_RECEIVING_BANKThe receiving bank rejected the transfer and returned the funds.
EXTERNAL_ACCOUNT_DEPOSIT_LIMIT_EXCEEDEDThe beneficiary account has exceeded its allowed deposit or incoming transfer limits.
EXTERNAL_ACCOUNT_HOLDER_ADDRESS_IS_PMB_OR_PO_BOXThe provided address is a PO Box or PMB, which is not accepted for compliance reasons.
RFI_NOT_ANSWERED_IN_TIMEA Request for Information (RFI) was not responded to within the required timeframe, causing the transfer to fail.
BLOCKED_BY_COMPLIANCEThe transaction was blocked due to compliance, regulatory, or risk checks.
RETURNED_AT_BENEFICIARY_REQUESTThe beneficiary requested the return of funds after the transfer was completed.
INVALID_ROUTING_CODEThe provided routing code (e.g., sort code, ABA, IFSC) is invalid or does not match the bank details.
BLOCKED_BY_INTERMEDIARY_BANKAn intermediary (correspondent) bank blocked or rejected the transaction during processing.

Sandbox Behavior

  1. Payments sent in Sandbox will not actually send to the intended account; the status will go straight to PROCESSING for payments with source.accountId and remain at AWAITING_FUNDS for payments without a source.accountId
  2. The source.accountId's fundingInstructions is dummy data. Do not send any funds to the bank or wallet address provided
  3. The exchange rates you see in Sandbox may be different than Production