Authentication & HMAC Guide
Cashia APIs (including Payment Gateway and Pay to Merchant flows) use HMAC SHA256 signatures to authenticate every request. This guide explains the shared requirements, header structure, and code samples you can reuse across products.
Required Headers
Include the following headers in every authenticated request:
X-Cashia-Key-ID: Your public API key IDX-Cashia-Nonce: Unique random string (minimum 12 characters)X-Cashia-Timestamp: Current Unix timestamp (seconds)X-Cashia-Hash: HMAC SHA256 over the request body (empty string for GET)X-Cashia-Signature: HMAC SHA256 over the signing string (see below)
Signing String Format
Concatenate the host, HTTP method (uppercase), timestamp, nonce, and key ID with no separators:
https://{host}{METHOD}{timestamp}{nonce}{keyId}Use your shared secret when generating both the body hash and the signature.
Step-by-Step Checklist
- Generate a nonce and timestamp.
- Hash the request body with HMAC SHA256 using your secret key.
- Build the signing string and hash it with the same secret key.
- Populate the headers listed above.
Node.js Example
const request = require('request');
const crypto = require('crypto');
function computeHmac256(message, secret) {
return crypto.createHmac('sha256', secret).update(message).digest('hex');
}
const secret = 'your-secret-key-here';
const host = 'https://api.cashia.example';
const method = 'POST';
const timestamp = Math.floor(Date.now() / 1000);
const keyId = '01927a78-d9af-75ab-a849-25a2b8f67be5';
const nonce = Math.random().toString(36).slice(2);
const body = JSON.stringify({ amount: 200, currency: 'KES' });
const signingString = `${host}${method}${timestamp}${nonce}${keyId}`;
const headers = {
'X-Cashia-Key-ID': keyId,
'X-Cashia-Nonce': nonce,
'X-Cashia-Timestamp': timestamp,
'X-Cashia-Hash': computeHmac256(body, secret),
'X-Cashia-Signature': computeHmac256(signingString, secret),
'Content-Type': 'application/json',
};
request({
method,
url: `${host}/api/v1/merchant-info`,
headers,
body,
}, (error, response) => {
if (error) throw error;
console.log(response.body);
});Go Example
package main
import (
"bytes"
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"net/http"
"time"
)
func computeHmac256(message, secret string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(message))
return hex.EncodeToString(h.Sum(nil))
}
func main() {
secret := "your-secret-key-here"
host := "https://api.cashia.example"
method := "POST"
timestamp := fmt.Sprint(time.Now().Unix())
keyID := "01927a78-d9af-75ab-a849-25a2b8f67be5"
nonce := "random-string"
payload := map[string]any{
"amount": 200,
"currency": "KES",
}
bodyBytes, _ := json.Marshal(payload)
signingString := host + method + timestamp + nonce + keyID
headers := map[string]string{
"X-Cashia-Key-ID": keyID,
"X-Cashia-Nonce": nonce,
"X-Cashia-Timestamp": timestamp,
"X-Cashia-Hash": computeHmac256(string(bodyBytes), secret),
"X-Cashia-Signature": computeHmac256(signingString, secret),
"Content-Type": "application/json",
}
req, _ := http.NewRequest(method, host+"/api/v1/merchant-info", bytes.NewBuffer(bodyBytes))
for k, v := range headers {
req.Header.Set(k, v)
}
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
defer resp.Body.Close()
fmt.Println("status:", resp.Status)
}Postman Helper Script
const CryptoJS = require('crypto-js');
const secret = pm.environment.get('secret');
const keyID = pm.environment.get('key_id');
const nonce = Math.random().toString(36).slice(2);
const timestamp = Math.floor(Date.now() / 1000);
const method = pm.request.method.toUpperCase();
const host = pm.environment.get('host');
const rawBody = (pm.request.body && pm.request.body.raw) || '';
const bodyHash = CryptoJS.HmacSHA256(rawBody, secret).toString(CryptoJS.enc.Hex);
const signingString = `${host}${method}${timestamp}${nonce}${keyID}`;
const signature = CryptoJS.HmacSHA256(signingString, secret).toString(CryptoJS.enc.Hex);
pm.environment.set('timestamp', timestamp);
pm.environment.set('nonce', nonce);
pm.environment.set('signature', signature);
pm.environment.set('body_hash', bodyHash);
pm.environment.set('Content-Type', 'application/json');Use this script in a Collection or Environment pre-request hook to automatically populate the headers before each call.
Last updated on