# Authentication

To ensure the security and integrity of system communication, Choice BaaS API requires all request senders to sign the request message with their private key, and the Choice BaaS platform will verify the signature before processing.​ If the signature is invalid, the request will NOT be accepted. <br>

## Sign the Request

After onboarding, a private key can be obtained from your account manager and every request you make should be signed with it. The signing algorithm is described below:

1. Fill the salt into the request JSON object with `salt` field name.
2. Fill the private key into the request JSON object with `senderKey` field name.
3. Convert the key-value pair of the request JSON object into string with alphabetical sorting. When converting, make the key-value pair as `string=string` and join them with `&`.
4. Hash the converted string which got in step 1 with plain SHA-256 and the private key then fill the hashed string back to the request JSON with field name - `signature`.
5. Remove the `senderKey` field from request JSON object.

### Steps

The original request JSON object is:

```json
{
    "requestId": "APPREQ00990320fed02000",
    "sender": "client1",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "params": {
        "name": "Tester"
    }
}
```

{% stepper %}
{% step %}

### Generate Salt

Generate the salt randomly and fill it into the request JSON object

```json
{
    "requestId": "APPREQ00990320fed02000",
    "sender": "client1",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZ123da",
    "params": {
        "name": "Tester"
    }
}
```

{% endstep %}

{% step %}

### Private Key

Fill the private key into the JSON object

```json
{
    "requestId": "APPREQ00990320fed02000",
    "sender": "client1",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZ123da",
    "senderKey": "yourkey",
    "params": {
        "name": "Tester"
    }
}
```

{% endstep %}

{% step %}

### Convert JSON to String

Flatten the JSON object to string.&#x20;

* ​**ASCII Order**: Sorted by raw byte values (e.g., `A` (65) before `a` (97)).
* ​**Joined with `&`**: Uses `&` as the delimiter between sorted key-value pairs.

<table><thead><tr><th width="410">JSON Object</th><th>Flat String</th></tr></thead><tbody><tr><td><pre class="language-json"><code class="lang-json">{
    "requestId": "APPREQ00990320fed02000",
    "sender": "client1",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZ123da",
    "senderKey": "yourkey",
    "params": {
        "name": "Tester"
    }
}
</code></pre></td><td>locale=en_ke&#x26;params.name=Tester&#x26;requestId=APPREQ00990320fed02000&#x26;salt=QcEwsZ123da&#x26;sender=client1&#x26;senderKey=yourKey&#x26;timestamp=1650533105687<br></td></tr></tbody></table>
{% endstep %}

{% step %}

### Hash the string

Hash the converted string with plain SHA-256 and fill the hashed into the field `signature`

```json
{
    "requestId": "APPREQ00990320fed02000",
    "sender": "client1",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZ123da",
    "senderKey": "yourkey",
    "signature": "cdfd996e7e5ca655d3fa663db03abe63b852669f04e1f82fda9b473f606a11",
    "params": {
        "name": "Tester"
    }
}
```

{% endstep %}

{% step %}

### Remove senderKey

Remove the field senderKey from the request JSON object. Then you get the final request JSON object and send out.

```json
{
    "requestId": "APPREQ00990320fed02000",
    "sender": "client1",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZ123da",
    "signature": "cdfd996e7e5ca655d3fa663db03abe63b852669f04e1f82fda9b473f606a11",
    "params": {
        "name": "Tester"
    }
}
```

{% endstep %}
{% endstepper %}

## Verify Signature

To verify the signature of the response from Choice BaaS

1. Fill the private key into the response JSON with field name `senderKey`.
2. Remove the field `signature` field from the response JSON.

{% hint style="warning" %}
**NOTES:** Please do remember to keep the value of the signature field of the response for comparison later.
{% endhint %}

3. Convert the modified response JSON to string with alphabetical sorting.
4. Hash the string converted from response JSON in step 3.
5. Compare the hash result from step 4 to the signature of the original response.

### Example

The original response JSON object:

```json
{
    "code": "00000",
    "msg": "Completed successfully",
    "requestId": "APPREQ00990320fed02000",
    "sender": "choice.baas",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZHMUr",
    "signature": "cdfd996e7e5ca655d3fa663db03abe63b852669f04e1f82fda9b473f606a11",
    "data": {
        "accountId": "46012123456789"
    }
}
```

{% stepper %}
{% step %}

### Private Key

Fill your private key to the field `senderKey`

```json
{
    "code": "00000",
    "msg": "Completed successfully",
    "requestId": "APPREQ00990320fed02000",
    "sender": "choice.baas",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZHMUr",
    "signature": "cdfd996e7e5ca655d3fa663db03abe63b852669f04e1f82fda9b473f606a11",
    "senderKey": "yourKey",
    "data": {
        "accountId": "46012123456789"
    }
}
```

{% endstep %}

{% step %}

### Remove Signature

Remove the field `signature` field from the response JSON.

```json
{
    "code": "00000",
    "msg": "Completed successfully",
    "requestId": "APPREQ00990320fed02000",
    "sender": "choice.baas",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZHMUr",
    "senderKey": "yourKey",
    "data": {
        "accountId": "46012123456789"
    }
}
```

{% endstep %}

{% step %}

### Convert JSON to String

<table><thead><tr><th width="418">JSON Object</th><th>Converted String</th></tr></thead><tbody><tr><td><pre class="language-json"><code class="lang-json">{
    "code": "00000",
    "msg": "Completed successfully",
    "requestId": "APPREQ00990320fed02000",
    "sender": "choice.baas",
    "locale": "en_KE",
    "timestamp": 1650533105687,
    "salt": "QcEwsZHMUr",
    "senderKey": "yourKey",
    "data": {
        "accountId": "46012123456789"
    }
}
</code></pre></td><td>"code=00000&#x26;data.accountId=46012123456789&#x26;locale=en_ke&#x26;msg=Completed successfully&#x26;requestId=APPREQ00990320fed02000&#x26;salt=QcEwsZ123da&#x26;sender=choice.baas&#x26;senderKey=yourKey&#x26;timestemp=1650533105687"<br></td></tr></tbody></table>
{% endstep %}

{% step %}

### Hash the converted string

Hash the converted string with **plain SHA-256**. (Note: not HMAC-SHA256)
{% endstep %}

{% step %}

### Compare Signatures

Compare the signature that 1. from the original response 2. from the hashed converted string.  If the two are the same, then the response is valid, otherwise, it's invalid.
{% endstep %}
{% endstepper %}

### Signing Algorithm in Java, Node.js, Ruby, Python

{% tabs %}
{% tab title="Java" %}

```java
// Dependencies: com.fasterxml.jackson.core:jackson-databind
import com.fasterxml.jackson.databind.ObjectMapper;
import java.security.MessageDigest;
import java.util.*; 

public class ChoiceBankSigner {

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /** Flatten any JSON-compatible object into key=value pairs. */
    private static void flatten(Object obj, String path, List<String> out) {
        if (obj == null) return;
        if (obj instanceof Map) {
            Map<?, ?> map = (Map<?, ?>) obj;
            if (map.isEmpty() && !path.isEmpty()) { out.add(path + "={}"); return; }
            for (Map.Entry<?, ?> e : map.entrySet()) {
                String child = path.isEmpty() ? e.getKey().toString() : path + "." + e.getKey();
                flatten(e.getValue(), child, out);
            }
        } else if (obj instanceof List) {
            List<?> list = (List<?>) obj;
            if (list.isEmpty()) { out.add(path + "=[]"); return; }
            for (int i = 0; i < list.size(); i++)
                flatten(list.get(i), path + "[" + i + "]", out);
        } else {
            out.add(path + "=" + obj);
        }
    }

    /** Build and sign a full Choice BaaS request body. Returns the map ready to serialize. */
    public static Map<String, Object> buildRequest(
            String sender, String requestId, String salt,
            Map<String, Object> params, String privateKey) throws Exception {

        Map<String, Object> body = new LinkedHashMap<>();
        body.put("requestId", requestId);
        body.put("sender",    sender);
        body.put("locale",    "en_KE");
        body.put("timestamp", System.currentTimeMillis());
        body.put("salt",      salt);
        body.put("params",    params);
        // signature excluded from flatten — computed below

        List<String> parts = new ArrayList<>();
        flatten(body, "", parts);
        parts.add("senderKey=" + privateKey);   // add BEFORE sort, NOT sent in body
        Collections.sort(parts);

        String stringToSign = String.join("&", parts);
        String signature    = sha256Hex(stringToSign);

        body.put("signature", signature);  // now add to body (not in the signed string)
        return body;
    }

    private static String sha256Hex(String input) throws Exception {
        MessageDigest md = MessageDigest.getInstance("SHA-256");
        byte[] hash = md.digest(input.getBytes("UTF-8"));
        StringBuilder sb = new StringBuilder();
        for (byte b : hash) sb.append(String.format("%02x", b));
        return sb.toString();
    }

    // --- Usage example ---
    public static void main(String[] args) throws Exception {
        Map<String, Object> params = new HashMap<>();
        params.put("accountNo", "1234567890");

        Map<String, Object> request = buildRequest(
            "YOUR_SENDER",
            "YOUR_SENDER" + UUID.randomUUID(),
            "randomSaltString",
            params,
            "YOUR_PRIVATE_KEY"
        );

        System.out.println(MAPPER.writerWithDefaultPrettyPrinter().writeValueAsString(request));
    }
}
```

{% endtab %}

{% tab title="Node.js" %}

```js
const crypto = require('crypto');

/** Recursively flatten an object into ["key=value", ...] */
function flatten(obj, path = '', out = []) {
  if (obj === null || obj === undefined) return out;
  if (Array.isArray(obj)) {
    if (obj.length === 0) out.push(`${path}=[]`);
    else obj.forEach((v, i) => flatten(v, `${path}[${i}]`, out));
  } else if (typeof obj === 'object') {
    const keys = Object.keys(obj);
    if (keys.length === 0 && path) out.push(`${path}={}`);
    else keys.forEach(k => flatten(obj[k], path ? `${path}.${k}` : k, out));
  } else {
    out.push(`${path}=${obj}`);
  }
  return out;
}

/**
 * Build a complete, signed Choice BaaS request body.
 * senderKey is used for signing only — it is NOT included in the returned object.
 */
function buildRequest({ sender, params, privateKey, salt }) {
  const requestId = sender + crypto.randomUUID();

  const body = {
    requestId,
    sender,
    locale:    'en_KE',
    timestamp: Date.now(),
    salt,
    params,
    // signature intentionally absent — computed below
  };

  const parts = flatten(body);
  parts.push(`senderKey=${privateKey}`);  // added BEFORE sort, NOT in the body
  parts.sort();

  const stringToSign = parts.join('&');
  console.log('String to sign:', stringToSign);

  body.signature = crypto.createHash('sha256').update(stringToSign).digest('hex');
  return body;  // ready to POST — no senderKey here
}

// --- Usage example ---
const request = buildRequest({
  sender:     'YOUR_SENDER',
  params:     { accountNo: '1234567890' },
  privateKey: 'YOUR_PRIVATE_KEY',
  salt:       'randomSaltString',
});

console.log(JSON.stringify(request, null, 2));

// Then POST this to the API:
// fetch('https://baas-pilot.choicebankapi.com/some/endpoint', {
//   method: 'POST',
//   headers: { 'Content-Type': 'application/json' },
//   body: JSON.stringify(request)
// })
```

{% endtab %}

{% tab title="Ruby" %}

```ruby
require 'digest'
require 'json'
require 'securerandom'

module ChoiceBankSigner

  # Recursively flatten a hash/array into ["key=value", ...]
  def self.flatten(obj, path = '', out = [])
    case obj
    when Hash
      if obj.empty? && !path.empty?
        out << "#{path}={}"
      else
        obj.each { |k, v| flatten(v, path.empty? ? k.to_s : "#{path}.#{k}", out) }
      end
    when Array
      if obj.empty?
        out << "#{path}=[]"
      else
        obj.each_with_index { |v, i| flatten(v, "#{path}[#{i}]", out) }
      end
    when NilClass
      # skip nils
    else
      out << "#{path}=#{obj}"
    end
    out
  end

  # Build a complete, signed Choice BaaS request body (Hash).
  # private_key is used only for signing — not present in returned hash.
  def self.build_request(sender:, params:, private_key:, salt:)
    request_id = sender + SecureRandom.uuid

    body = {
      requestId: request_id,
      sender:    sender,
      locale:    'en_KE',
      timestamp: (Time.now.to_f * 1000).to_i,
      salt:      salt,
      params:    params
      # signature absent here — computed below
    }

    parts = flatten(body)
    parts << "senderKey=#{private_key}"  # added BEFORE sort, NOT sent in body
    parts.sort!

    string_to_sign = parts.join('&')
    puts "String to sign: #{string_to_sign}"

    body[:signature] = Digest::SHA256.hexdigest(string_to_sign)
    body  # return — ready to POST as JSON, no senderKey inside
  end
end

# --- Usage example ---
request = ChoiceBankSigner.build_request(
  sender:      'YOUR_SENDER',
  params:      { accountNo: '1234567890' },
  private_key: 'YOUR_PRIVATE_KEY',
  salt:        'randomSaltString'
)

puts JSON.pretty_generate(request)
```

{% endtab %}

{% tab title="Python" %}

```python
import hashlib, time, uuid, json

def flatten(obj, path='', out=None):
    """Recursively flatten a dict/list into ['key=value', ...] pairs."""
    if out is None:
        out = []
    if obj is None:
        return out
    if isinstance(obj, dict):
        if not obj and path:
            out.append(f"{path}={{}}")
            return out
        for k, v in obj.items():
            child = k if not path else f"{path}.{k}"
            flatten(v, child, out)
    elif isinstance(obj, list):
        if not obj:
            out.append(f"{path}=[]")
            return out
        for i, v in enumerate(obj):
            flatten(v, f"{path}[{i}]", out)
    else:
        out.append(f"{path}={obj}")
    return out

def build_request(sender: str, params: dict, private_key: str, salt: str) -> dict:
    """
    Build a complete, signed Choice BaaS request body.
    private_key is used only for signing — it is NOT present in the returned dict.
    """
    request_id = sender + str(uuid.uuid4())

    body = {
        "requestId": request_id,
        "sender":    sender,
        "locale":    "en_KE",
        "timestamp": int(time.time() * 1000),
        "salt":      salt,
        "params":    params,
        # "signature" intentionally absent — computed below
    }

    parts = flatten(body)
    parts.append(f"senderKey={private_key}")  # added BEFORE sort, NOT sent in body
    parts.sort()

    string_to_sign = "&".join(parts)
    print(f"String to sign: {string_to_sign}")

    body["signature"] = hashlib.sha256(string_to_sign.encode("utf-8")).hexdigest()
    return body  # ready to POST — no senderKey here

# --- Usage example ---
request = build_request(
    sender      = "YOUR_SENDER",
    params      = {"accountNo": "1234567890"},
    private_key = "YOUR_PRIVATE_KEY",
    salt        = "randomSaltString",
)

print(json.dumps(request, indent=2))
```

{% endtab %}
{% endtabs %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://choice-bank.gitbook.io/choice-bank/getting-started/authentication.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
