Skip to main content

Overview

The CommerceCash Payment API uses HMAC-SHA256 request signing to authenticate and verify the integrity of payment initiation requests. Each merchant is issued a unique Secret Key which is used to generate the cc-signature header.
The cc-signature header is currently required for the Initiate Payment endpoint. Requests without a valid signature will be rejected with INVALID_SIGNATURE.

Request Header

Include the following header on every Initiate Payment call:
HeaderTypeRequiredDescription
cc-signaturestring✅ Yes (Initiate)HMAC-SHA256 of the raw JSON request body, Base64-encoded

Signature Generation

Algorithm

Pseudocode:
cc-signature = Base64( HMAC-SHA256( secretKey, rawRequestBody ) )

Rules

1

Serialise the request body to camelCase JSON

The signing input is the complete raw JSON request body sent in the HTTP request, serialised using camelCase property names.
{
  "merchantId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
  "referenceNo": "MER-20240616-001",
  "amount": 15000,
  "currencyCode": "MYR",
  "productCode": "TNB",
  "storeId": "STORE-001",
  "extraProperties": "{\"accountNumber\":\"123456\"}"
}
The JSON string sent in the HTTP body must be byte-for-byte identical to the string used for signing. Do not re-serialise or reformat the body after computing the signature.
2

Compute HMAC-SHA256

Compute HMAC-SHA256 using:
  • Key: your merchant Secret Key (UTF-8 encoded bytes)
  • Data: the raw JSON string from Step 1 (UTF-8 encoded bytes)
3

Encode the result as Base64

Convert the raw HMAC-SHA256 byte array to a Base64 string. This is the value you place in the cc-signature header.

Code Examples

using System.Security.Cryptography; 
using System.Text; 
using System.Text.Json;

var requestBody = new { merchantId = "3fa85f64-5717-4562-b3fc-2c963f66afa6", referenceNo = "MER-20240616-001", amount = 15000, currencyCode = "MYR", productCode = "TNB", storeId = "STORE-001" };

var jsonOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase };

var rawBody = JsonSerializer.Serialize(requestBody, jsonOptions); 
var secretKey = "your-merchant-secret-key";

using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)); 
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(rawBody)); 
var signature = Convert.ToBase64String(hash);

// Add to HttpClient request: 
// request.Headers.Add("cc-signature", signature);

Error Responses

ScenarioHTTP StatuserrorCodemessage
cc-signature header is invalid400INVALID_SIGNATURE"Invalid request signature"
Merchant secret key not configured400INVALID_SIGNATURE"Signature secret key not configured for merchant: {merchantId}"

Example 400 Response

{
  "isSuccess": false,
  "errorCode": "INVALID_SIGNATURE",
  "message": "Invalid request signature",
  "data": null
}

Security Notes

The server compares signatures using CryptographicOperations.FixedTimeEquals, which prevents timing-based side-channel attacks. Do not attempt to probe the signature by varying bytes.
Store your Secret Key in a secrets manager or environment variable. Never hard-code it in client-side code or commit it to source control.
Because the signature is computed over the full request body, any modification to the payload in transit will cause signature validation to fail, protecting against tampering.