REST API Authentication

Wello provides our partners with APIs to access essential data, such as trading pairs, quotes, order details, and more. This guide outlines the necessary components for request headers, how to generate a signature, and provides examples in JavaScript and Postman for making API requests.

Initiate a request

All REST private request headers must contain the following:

  • x-api-clientid: The public key you generate using the SHA256withRSA (see “Generate Key Pair” section)
  • x-api-timestamp: UNIX millisecond timestamp indicating when the request is made.
  • x-api-nonce: A unique 32-digit random string. For example, you can generate a random ASCII number and loop it 32 times to create the nonce.
  • x-api-signature: Use the secret key generated with SHA256withRSA (see "Generate Key Pair") to generate the hash, then encode it using Base-64 (see "Signature" section).

All requests must use the “application/json” and be a valid JSON.

Signature

x-api-signature request header is generated from the following logic:

  1. Compose your request body:
    1. Depending on your http method, convert the parameters below into the array of key-value pairs:
      1. POST method: body parameters
      2. GET method: query parameters
    2. Add x-api-clientid, x-api-timestamp, and x-api-nonce values into the request body and join all elements of the array using “&”, and use the SHA256 method and RSA algorithm to encrypt the appended parameters. Once it is done, encode it using Base64.
  2. x-api-timestamp uses unix timestamp as of now, for example 1728915293954
  3. The body generated for signature should be like “x-api-clientid=merchant-test&x-api-timestamp=1730443325201&x-api-nonce=qwNru8GFuuF6fUIJIYQghgb1davI4pou”, if no query payload or parameters required.
🚧

Order for signature payload

There is no requirement for order for query params and body params, however, we have requirement for order for x-api-headers. You need to have clientid first, timestamp second, the nonce the third in order to get your signature right for verification or else, you will get verification failure error in the response.

Exact order as: "x-api-clientid=merchant-test&x-api-timestamp=1730443325201&x-api-nonce=qwNru8GFuuF6fUIJIYQghgb1davI4pou"

📘

Array format for signature

Currently we only have walletAddresses as array required for x-api-signature, please refer to the Java example to see how you should format it. Or you may follow the format below:

"&walletAddresses=[{network=BTC, address=XXX}, {network=SETH, address=XXX}, {network=ETH, address=XXX}][{network=BTC, address=XXX}, {network=SETH, address=XXX}, {network=ETH, address=XXX}]" to generate required signature.

This is also applied for Webhook Signature Verification.

Please check the Javascript/Java example below for how it is done:

//Compose the request body
StringBuffer requestBodySb = new StringBuffer();
if (Objects.nonNull(requestBody) && !"".equals(requestBody)) {
    Map<String, Object> requestBodyMap = JacksonUtil.fromMap(JacksonUtil.toJsonStr(requestBody));
    TreeMap<String, Object> sortedBody = new TreeMap<>(requestBodyMap);
    sortedBody.forEach((k, v) -> {
        if (v != null && !"".equals(v)) {
            requestBodySb.append(k).append("=").append(v).append("&");
        }
    });
}
String body = requestBodySb
              .concat("x-api-clientid=").concat(clientId)
              .concat("&x-api-timestamp=").concat(timestamp)
              .concat("&x-api-nonce=").concat(nonce);

//Sign the request body
Signature signature = Signature.getInstance("SHA256withRSA");
PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(Base64.getDecoder().decode(privateKey));
PrivateKey key = KeyFactory.getInstance("RSA").generatePrivate(keySpec);
signature.initSign(key);
signature.update(body.getBytes(StandardCharsets.UTF_8));
Base64.getEncoder().encodeToString(signature.sign());
function generateNonce(length) {
   // Generate a random ASCII string of the specified length
   let nonce = '';
   const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
   for (let i = 0; i < length; i++) {
       nonce += chars.charAt(Math.floor(Math.random() * chars.length));
   }
   return nonce;
}


const nonceSet = new Set();


function generateUniqueNonce(length) {
   let nonce;


   do {
       nonce = generateNonce(length);
   } while (nonceSet.has(nonce));


   nonceSet.add(nonce);
   return nonce;
}


function buildQueryString(requestBody, clientId, nonce, timestamp) {
    let requestBodySb = [];

    if (requestBody !== null && requestBody !== "") {
        const requestBodyMap = Object.entries(requestBody);
        const sortedBody = requestBodyMap.sort(([keyA], [keyB]) => keyA.localeCompare(keyB));

        sortedBody.forEach(([k, v]) => {
            if (v !== null && v !== "") {
                if (k === 'walletAddresses' && Array.isArray(v)) {
                    // convert walletAddresses into set format
                    const walletAddressesStr = v.map(addressEntry => {
                        return `{network=${addressEntry.network}, address=${addressEntry.address}}`;
                    }).join(', ');

                    requestBodySb.push(`walletAddresses=[${walletAddressesStr}]`);
                } else {
                    // deal with other key value pairs
                    requestBodySb.push(`${k}=${v}`);
                }
            }
        });
    }

    // add extra headers info
    requestBodySb.push(`x-api-clientid=${clientId}`);
    requestBodySb.push(`x-api-timestamp=${timestamp}`);
    requestBodySb.push(`x-api-nonce=${nonce}`);

    return requestBodySb.join("&");
}


// Example usage
const requestBody = {
    merchantCode: 'merchant-test',
    side: 'BUY',
    cryptoCurrency: 'ETH',
    network: 'ETH',
    fiatCurrency: 'EUR',
    requestCurrency: 'EUR',
    requestAmount: 100,
    paymentMethodType: 'SEPA',
    walletAddresses:
      [
      {
          network: 'BTC',
          address: 'XXXX'
      },
      {
          network: 'SETH',
          address: 'XXXX'
      },
      {
          network: 'ETH',
          address:  'XXXX'
      }
      ],
};


const queryString = buildQueryString(requestBody);
console.log(queryString);


import { createSign } from 'crypto';
function sign(body, privateKey) {
   try {


       // create Signature
       const sign = createSign('SHA256');
       sign.update(body);
       sign.end();


       // Base-64 encoded Signature
       // Use privateKey (PEM encoded) for signature directly
       const signature = sign.sign(privateKey, 'base64');
       return signature;
   } catch (error) {
       console.error('signature failure:', error);
       return null;
   }
}
const body = buildQueryString(requestBody);
const privateKey = 'YOUR_PEM_ENCODED_PRIVATE_KEY';
const signature = sign(body, privateKey);
console.log('generated signature:', signature);