Atlas for Fintech - Payout Migration Guide

This guide outlines the critical architectural and payload changes moving from the legacy Orders FX API (v2.1) to the new Exchange Payments API.

Overview of Major Architectural Changes

Endpoint URL Change: The base domain and path have changed.

Unified Flow: The new API consolidates what used to be multiple actions (FX between wallets vs. FX + Payout) into a single unified endpoint controlled by the paymentType field.

Deprecations:
Legacy Beneficiary Creation and Wallet Withdrawal endpoints are fully deprecated. The new API handles internal wallet routing and external payouts directly.

Breaking Change 1: Authorization & Authentication

Before interacting with any updated v1.1 endpoints, all Atlas clients must update their Authentication setup, as the new Login API endpoint requires a certificated based authentication and different URL.

Why is this a breaking change?

If you try to send your plain, raw API key from the Legacy Login method to the new system, the connection will be rejected.

Additionally, because a live timestamp is mixed into the package during encryption, the login request automatically expires after 30 seconds.

Your backend must create this encrypted package dynamically right before making the login call. To get started on migrating the login:

  1. Change your login URL:
  1. Swap the Value:

In your JSON request body, keep the parameter name exactly as "apiKey", but replace your plain text key with your newly encrypted text block.

{
  "clientId": "YOUR_28_DIGIT_CLIENT_ID",
  "apiKey": "RAW_STATIC_API_KEY_STRING",
  "mode": "apiKey"
}
{
  "clientId": "YOUR_28_DIGIT_CLIENT_ID",
  "apiKey": "eyJKdW5lMTg...[Encrypted Base64 String Input]...",
  "mode": "apiKey"
}

For more technical examples, required encryption settings, and instructions on how to download your digital certificate from the dashboard, please see the Authentication Guide and the Certificate-Based Authentication Guide.

Breaking Change 2: Beneficiary Creation

The core process remains the same i.e you are still registering a recipient's payout details. However, the new API introduces a cleaner structure, introduces stablecoin capabilities, and enforces explicit payment modes up front.

Key Endpoint Changes in Beneficiary creation:

**⚡ Critical Breaking Changes **

Before writing your new code, please make sure you accommodate these four foundational changes:

  1. The paymentMode Field is Now Mandatory: instead of using a generic endpoint, you must now explicitly state how the money is moving. For Global Clearing (USD, GBP, EUR, etc.): Use LOCAL or INTERNATIONAL. For Last Mile Mobile Money (KES, TZS, XAF): Use MOBILE_MONEY.
  2. Currency Strings vs. Currency IDs
    • Old: You passed a 3-letter code text string (e.g., "currency": "AED").
    • New: You must now pass a numeric ID mapping to that currency (e.g., "currencyId": 4).
  3. Structural Field Renaming Several parameter names have changed to align with universal payment standards:
    • beneficiaryEntityType is now beneficiaryType.
    • nationalId (where you used to put Swift/Sort/Routing codes) is now bankCode.
  4. Structured Addresses
    Instead of passing flat text strings for countries and addresses, both the bank's address and the beneficiary's physical address must now be passed as dedicated objects (bankAddress and beneficiaryAddress).

Side by Side Request Comparison using examples

{
  "beneficiaryEntityType": "individual",
  "currency": "AED",
  "isMobileMoney": false,
  "nationalId": "01HW0JA",
  "accountNumber": "1-021-2",
  "beneficiaryCountryCode": "ke",
  "beneficiaryEmail": "[email protected]",
  "beneficiaryFirstName": "Chris",
  "beneficiaryLastName": "Jack",
  "country": "India"
}
{
  "paymentMode": "INTERNATIONAL",
  "beneficiaryType": "individual",
  "accountNumber": "98846622175",
  "bankCode": "PUNB0644100",
  "bankName": "Bank of India",
  "firstName": "Chris",
  "lastName": "Jack",
  "currencyId": 4, 
  "partyType": "First_Party",
  "bankAddress": {
    "countryCode": "IN",
    "stateOrProvience": "Delhi",
    "city": "Delhi",
    "postCode": "120121",
    "address": "Downtown street"
  },
  "beneficiaryAddress": {
    "countryCode": "KE",
    "stateOrProvience": "Nairobi",
    "city": "Nairobi",
    "postCode": "1021212",
    "addressLine1": "Downtown Road",
    "email": "[email protected]"
  }
}

Breaking Change 3: Get FX Rate

Migrating from the old Get an FX Rate endpoint to the new one introduces several critical breaking changes. You will need to rewrite the HTTP request method, the URL endpoints, and entirely restructure the data payload.

Here is a detailed breakdown of the breaking changes:

HTTP Method Change(GET to POST)

Old API: Used a GET request, meaning parameters were passed visibly in the URL string.

New API: Uses a POST request. Parameters must now be passed securely inside the HTTP request body as a JSON payload.

Impact: High. Any code natively calling this as a GET request will fail with a 405 Method Not Allowed or 404 Not Found error.

Base URL and Route Updates

Old API: https://api-v3-sandbox.vertofx.com/orders/v2.1/fx

New API: https://api-exchange-now-beta.vertofx.com/fx/rate

Impact: Medium.
The host domain and path have completely changed. Ensure your environment variables/configuration files are updated.

Payload Restructuring (Flat Query String to Nested Objects)

The parameter format has changed from flat query strings to objects inside a JSON body.

Legacy API: Passed strings directly (currencyFrom=USD&currencyTo=NGN).

https://api-v3-sandbox.vertofx.com/orders/v2.1/fx?currencyFrom=GHS&currencyTo=USD' 

New API: Requires currencyFrom and currencyTo to be formatted as JSON objects, not flat strings.

{
  "paymentMode": "immediate",
  "currencyFrom": {
    "currencyName": "GBP"
  },
  "currencyTo": {
    "currencyName": "NGN"
  },
  "amountCurrency": "GBP",
  "amount": 5000
}

Passing strings like "currencyFrom": "USD" in the new payload will result in a 400 Bad Request schema validation error.

New Mandatory Parameters (amount and amountCurrency)**

The Legacy API only required the currency pair (currencyFrom and currencyTo). You did not have to provide the transactional volume to get a general rate. The New API now introduces mandatory amount and amountCurrency parameters into the mix.

Based on the business use case (collections), your system must now calculate or explicitly pass the incoming fund amount amount and its respective currency amountCurrencybefore fetching a rate.

Introduction of paymentMode

The Legacy API had no logic for settlement timing on the rate fetch layer. The New API introduces an optional paymentMode parameter which defaults to immediate, but allows later.

If left out, it defaults to immediate, but it gives your system new flexibility if you intend to book forward trades or delayed settlement cycles.

Breaking Change 4: Create FX Trade

The new API (/fx/payments) is much more robust, treating FX trades as unified payment flows. It replaces the old side-based system (BUY/SELL) with explicit source/target amounts and introduces wallet identifiers for a more secure and descriptive transaction structure.

Here are the key changes you need to make to your integration.

Endpoint URL Update

The API base URL and version routing have completely changed.

Legacy URL: POST https://api-v3-sandbox.vertofx.com/orders/v2.1/fx

New URL: POST https://api-exchange-now-beta.vertofx.com/fx/payments

Request Payload Restructuring (Field-by-Field Mapping)

Payload properties have been renamed, restructured, or replaced.

Legacy Field

New Field

Change Type

Description / Migration Action

vfx_token

vfxToken

Breaking

Snake case changed to camelCase. Still required and bound to the Get Rate validity window

side (BUY / SELL)

currencyFrom, currencyTo, amountCurrency

Breaking

(Core Logic ) The side enum is removed. Instead of specifying a BUY/SELL side direction against an implicit currency pair, you must explicitly pass the source/target objects and designate which currency the amount applies to using amountCurrency.

amount

amount

Modified Logic

While the field name is the same, its behavior now depends on amountCurrency rather than the legacy side orientation.

clientReference

customPaymentReference

Breaking

Renamed. Max length and validation rules should be verified against the new schema.

--

paymentType

Breaking

New Required Field.
Must be explicitly set to convertWithinWallets for the wallet-to-wallet flow.

--

targetWalletId

Breaking

New Required Field.
You must now explicitly pass the destination wallet ID.


Handling the Removal of side (BUY / SELL)

In the new API, you no longer specify side. Instead, you define either sourceAmount or targetAmount inside the sources array based on what you know:

  • If your Legacy Side was SELL: (You are selling a fixed amount of the source currency).Map amount to sourceAmount.
  • If your Legacy Side was BUY: (You want to buy a fixed amount of the destination currency).Map amount to targetAmount.

🔄 Payload Comparison (Flow 1: Wallet-to-Wallet) Below is an explicit comparison of how to change your request payload to perform an intra-wallet FX trade.

Legacy Request Example:

{
  "vfx_token": "tok_1234567890",
  "side": "SELL",
  "amount": 5000,
  "clientReference": "InternalRef123"
}
{
  "paymentMode": "immediate",
  "paymentType": "convertWithinWallets",
  "vfxToken": "tok_1234567890",
  "currencyFrom": {
    "currencyName": "GBP"
  },
  "currencyTo": {
    "currencyName": "NGN"
  },
  "amountCurrency": "GBP",
  "amount": 5000,
  "targetWalletId": 98765,
  "customPaymentReference": "InternalRef123"
}


Handling the Removal of side (BUY / SELL)

The response payload is now flatter and provides significantly more metadata regarding pricing, settlement times, and state tracking.

Order Object Nesting: The response no longer wraps the transaction details inside an order block. All core fields are now at the root level of the JSON response.

Currency Fields: Previously returned only if successful, currencyFrom and currencyTo are explicitly returned at the root.

Status Tracking: The fields transactionState and status have been consolidated and expanded. Look at the state field (e.g., initiated, confirmed, inwardSettlementDone) to track progress.

Response Body Changes

The response payload is now flatter and provides significantly more metadata regarding pricing, settlement times, and state tracking.

  • Order Object Nesting: The response no longer wraps the transaction details inside an order block. All core fields are now at the root level of the JSON response.
  • Currency Fields: Previously returned only if successful, currencyFrom and currencyTo are explicitly returned at the root.
  • Status Tracking: The fields transactionState and status have been consolidated and expanded. Look at the state field (e.g., initiated, confirmed, inwardSettlementDone) to track progress.
Legacy Response FieldNew Response FieldNotes
order.ididNow returned as a String at the root level.
order.referencereferenceRemains a string reference code
order.amountFromamountFromRoot level float
order.amountToamountToRoot level float
order.raterateReturned as a numeric type instead of a string
order.transactionStatestateUpdated enum values (initiated, confirmed, etc.)

Breaking Change 3: Payout to Beneficiary

Migrating your payout logic from the old legacy withdrawal setup to the new system introduces a complete shift in endpoints and data models. The new architecture transitions away from generic requests to specialized, explicit routes based on where the funds are going.

Endpoint URL Update

The single "catch-all" legacy endpoint has been decoupled into specific destinations. To initiate a payout from a wallet to an external beneficiary or bank account, use the new dedicated payments API endpoint.

Request Payload Restructuring (Field-by-Field Mapping) Properties have been decoupled, renamed, and data types have shifted from numbers to strict string configurations.

⚡ Critical System Enforcements Strict Type Coercion: Passing numeric types for sourceWalletId, targetAccountId, or purposeId will cause immediate 400 Bad Request schema validation failures. They must be wrapped as strings.

Feature Additions: The new payout payload unlocks scheduling capabilities. If you want to delay or repeat payouts, you can now pass optional fields like scheduledDate(ISO 8601 string) and recurFrequency (ONE_TIME, WEEKLY, MONTHLY).

🔄 Payload Comparison (Wallet to Bank Beneficiary) Below is an explicit comparison of how to rewrite your request payload to perform an external beneficiary bank payout.

{
  "walletId": 12345,
  "beneficiaryId": 67890,
  "amount": 1500,
  "purposeId": 101,
  "clientReference": "Invoice-99A",
  "paymentId": "4a7b-b89c-01123cd45e"
}
curl --request POST \
     --url https://api-payment-sandbox.vertofx.com/payments/create \
     --header 'accept: application/json' \
     --header 'content-type: application/json' \
     --data '
{
  "paymentType": "WALLET_PAYOUT",
  "sourceWalletId": "12345",
  "sourceAmount": 1000.5,
  "targetAccountId": "67890",
  "purposeId": "1",
  "customPaymentReference": "Invoice payment #12345",
  "recurFrequency": "WEEKLY",
  "scheduledDate": "null"
}
'