Merchant API

Take the Xend Finance services to your customers via a RESTful API and webhooks.

Who is a merchant

A merchant is a registered business that performs savings operations on behalf of their customers. Basically, your account is referred to as a merchant account and you can create your customers’ accounts as sub accounts. Each sub account has access to a personal wallet address and you can perform transactions (transfer, deposit, save, withdraw, and more) on each account.

What is this?

This API is intended for Finance Applications that wish to leverage the Xend Finance savings service and offer it to their end users. You can, via the API, create an account for each of your customers and manage their accounts just the same way a normal user would inside the Xend Finance mobile application to save, earn interest, transfer money, and much more.

Link to Merchant Swagger doc: Sample nodeJs application:

Becoming a merchant

Staging/sandbox environment

  1. Create a merchant account for the sandbox environment by calling the merchant auth endpoint /public/create/sandbox/account with the required email and password. Please check out the swagger documentation for more clarity on the API endpoint.

Production environment

The steps below are for setting up a merchant account in a production environment.

  1. Download the Xend Finance Mobile app.

  2. Register and verify your account using the mobile app with your company email address.

  3. Follow the steps in the mobile app and complete your user profile.

  4. Next, on the mobile app, proceed to the profile section. Then, under Account Security, enable your 2-Factor Authentication.

  5. Next, complete the KYC Verification still in the profile section of the mobile app using CEO profile.

  6. Complete the KYB and business documentation: A link will be provided where you complete our KYB process by uploading your business profile and documents. Ref:

  7. Verify all your provided data for accuracy and authenticity

The return data of the /public/create/sandbox/account and /public/register API will have information on apiKey and apiSecret which you will use to access the rest of the API endpoints for merchants. You can keep them in a safe place where you can easily use them for subsequent merchant API calls.

API Access

To access any authenticated endpoints from the Merchant API you have to add the following properties to the request headers.

  1. authentication: JWT key that is returned to you after logging in.

  2. apisignature: Api signature calculation from the data used to make the HTTP request. An example of how it is calculated can be found in the section "API Signature Calculation"

  3. timestamp: this should be the current timestamp in milliseconds UTC

  4. memberemail: email of the sub-user of the merchant's platform.

  5. merchantcode: merchant user id generated after creating your account.

  6. userlanguage: the language that the responses from the API should be. Use the ISO language name as the value of the field.

  7. authorization: serves as the API captcha. For the test environment, the value is Basic parole.

API Signature Calculation

Merchant API endpoints make use of HMAC SHA256 signatures for authenticating API requests. Hash-based message authentication code (HMAC) is used to verify that a request to our endpoint is coming from a trusted source and that the request has not been tampered with on transit from the source of the request to our servers.

The parameters needed to calculate the API signature include timestamp, clientToken, clientSecret, and the payload of the request. All these values are concatenated in URL encoded format and the hash is calculated with the SHA256 hashing algorithm.

// Example code for calculating API signature
import cryptoJS from 'crypto-js';

const timestamp = '';
const token = '';
const accessSecret = '';

const payload = {
  username: 'bob',
  email: ''

const signaturePayload = 'username=bob&';
const signatureText = `${signaturePayload}&timestamp=${timestamp}&api-key=${token}&api-secret=${accessSecret}`;

const signature = cryptojs.HmacSHA256(signatureText, accessSecret).toString(cryptojs.enc.Hex);

HMAC with Postman

Postman has a feature to add a pre-request script for every request it makes. We have an example of the script to calculate the API signature and add it to your request headers. Remember to add the following variables to Postman, hmac, timestamp, token, accessSecret.

JSON Response

Every response body is expected to be JSON data but the API gateway could return unrecognized HTML content with Gateway errors when something goes wrong.

    "data": {},
    "status": "success",
    "status_code": 200,
    "message": "action completed",
    "error_code": "",
    "action": null,
    "message_language_code": "",
    "error_details": null
  • data: the data returned by the response can be user profile and token, for example, in the login endpoint. The data can be an empty object {}, empty array [], or null.

  • status: The action status enum (success, failed). E.g. if login fail, status will be failed status_code: this is the request header status code. A failed request can have any error code based on section 1 A.

  • error_code: Error code based on error documentation, each error for example if during registration we discover a user trying to use a temporary email address, the error code is documented such that even the public can read our error documentation and know what it means. So, invalid email address can be error code 0001. Ref: Error Code Documentation

  • action: this defines an action that could be taken after an error is returned. E.g. load_2fa_login REF: Response Action Documentation. Secondly, it can return the field, causing the error. E.g. if you are doing authentication and providing email and password. The action can be =”password” meaning that the error is caused by param “password”.

  • message: this is the error message that is shown to the user. This can be, e.g., “wrong username and password”. It is returned for every request.

  • response_details: here comes the debug message and log for the developer; this should never be shown to the user, as the developer can easily discover what he is doing wrong here. E.g., if you pass the user phone number as a string instead of number and the API rejects it, the ‘message’ will say: “something is wrong, try again later.”, but the ‘response_details’ will say, “Please phone number must be set as number,”. The response detail actually comes with error logs.

  • message_language_code: every response comes with language code which is the response.message (front-end can use the language code to switch the message shown to the user based on language preference of the user). The API may return only English ‘message’ for the user – with this code corresponding to the language code doc so the code can be used for translation.

  • Translation: for languages, users can pass language code in every request and the API will return the message in their language (see language code documentation). Also, the language code is returned in the response. Front-end can use the language code to make translations on the app. REF: language code doc

Language filter is passed thus:

User.profile.userlanguage = eng (user must have set language in his profile)

Post.Header.userlanguage = eg (Pass language in header in each request to return error messages in that language.) NB: language in header overrides user language in profile.

Request Body and Query

Every POST/PUT/PATCH/DELETE Requests will reject Query Strings.

Pass Query Strings to GET Request only.

Also, POST/PUT/PATCH/DELETE Requests do not want to expect URL params or query strings.

NB: what is query string, query param, body param?


Date in body param must be posted in this format: eg expiry_time: "2022-04-09 06:05:05"

Limits and quotas on API requests

  • 50,000 requests per project per day, which can be increased

  • 10 queries per second (QPS) per IP address. (And 100 requests per 100 seconds per member) — when your request limit is reached, header status can be 503

API request is limited/hr you are advised to manage how you query the API to avoid exhausting the limits.

Response header status

The response header status codes are described in the table below.

Status codeTypeDescriptionDetails



The status code 200 does not always mean that your action is complete. Check the doc for JSON Response object describing actions

It means your http request is complete and ok.


Expectation Failed

Seen when a user posts the data that is beyond the validation. E.g. if you post amount 20,000 when max amount is stated to be 10,000

The JSON Response sample exist in APP_INFO section of swagger doc.


Bad Request

Not usually caused by the user. Basically, if this is seen, the programmer did not read documentation. I advise you to read them. They don’t bite

This can happen for example if you make a request to an endpoint without providing all the required params in header or body.


Not Found

The 404 response code does not means the data you request is not found. It's basically the route not found and this status code can come with json object or even HTML data; don't be surprised


Internal Server Error

Can be returned if something is wrong on the server or you, the programmer, has configured and posted unwanted data that attempt to confuse us. This status code can come with JSON OBJECT or even HTML data; dont be taken unawares.



As we use cloudflare DNS, this header error code can be seen. It can return HTML, or any other sophisticated data types that you have not seen before


Not Authorized

When the Member authentication keys are not set or have expired



When you do what you are not supposed to do.

If you try to do 2fa login when the user has not enabled 2fa, you can get this code


Service Unavailable

Sometimes while we are installing updates on the server, or your request limit is exhausted, this could happen

Wait for a second to continue.


Too Many Requests

When bots or users are sending too many requests to the API endpoint.

Response data can be mixed format. Follow standard and identify Retry Time in header


Every endpoint with pagination will have data, thus:

// Here, array of the items returned and their content field are self-explanatory
 DATA."paginator": {
        "itemCount": 9,
        "perPage": 2,
        "pageCount": 5,
        "currentPage": 1,
        "pagingCounter": 1,
        "hasPrevPage": false,
        "hasNextPage": true,
        "prev": null,
        "next": 2
 // Here, 
 // itemCount = total number of items. Others are self-explanatory.
 // prev = previous page number

Switching page in pagination

Set query params:

  1. pageId | INT (the page you wish to load: default is 1)

  2. perPage | INT (number of items you want to return on each page.. Default is 18, or 20)

  3. sort | String (ENUM >> desc,asc) (sort order for the items) default = desc

NB: Please check swagger swagger/doc/index.html/#/APP_INFO for sample pagination data (GET/info/app/pagination/sample THANKS)

Auto Save (Flexible & Fixed)

Auto save means the user sets his auto save configuration such that once funds enter the wallet we can auto save it to the savings plan either flexible or fixed.

NB: auto fixed savings overrides flexibles savings (should the user enable both on any wallet)

Globally, we can enable or disable auto saving in the entire system.

On each Currency we can enable / disable auto save too.

How autosave works

User sets auto save config in his wallets. Any time the fund enters the user wallet we create a job to auto save the fund in 12 hrs. If in 12 hrs time we try to auto save the fund and the fund is no longer there, we drop the job process. *Fixed savings require the user to select the savings profile that the auto save will top up the fund or leave it empty for a new one to be created.

*users are not allowed to select fixed saving profile yet

Auto save config settings




Whether to save to fixed when money enter the wallet

This when set to true overrides autoSaveFlexible config





Whether to reinvest the income from fixed savings when it matures.



Whether to save all the funds in the wallet or the amount received when the trigger happen

Default = true



Should the auto save be for fixed savings, we need duration to use

Multiple of 30

Call back & Webhooks

Edit profile and set your webhook URL. We enable only one URL with module filters. What we offer here is an Unauthenticated POST RESTful webhook over HTTPS only.

NB: callbacks are not guaranteed.

Webhook parameters

ParameterModule / ServiceDescriptionAvailable Examples



The service module. You can get a webhook when a user account is funded with

  1. wallet



We will pass the secret you provided in all callback

All module means all the callback we give you must have this param passed which is “secret” same with “service” all webhook will have “service”



Amount funded in wallet when deposit is received in the blockchain.



could be BUSD etc



Could be the crypto address that got the credit.



Associated user Email

If it’s wallet, then it could be the user that received the funding

WhatsApp Integration Community

Join now! You can get instant answers and also get a test BUSD on SANDBOX.


  1. The database time zone is UTC.

  2. Here's the link to the swagger documentation.

  3. A sample node.js app of the Merchant API integration.

Last updated