Skip to Content
DocumentationShared GuidesAuthentication

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 ID
  • X-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

  1. Generate a nonce and timestamp.
  2. Hash the request body with HMAC SHA256 using your secret key.
  3. Build the signing string and hash it with the same secret key.
  4. 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