Important
When you change your checksum settings (turning on/off), you must regenerate your API tokens to ensure continued access to the API. Existing tokens will become invalid when checksum settings are modified.
When you change your checksum settings (turning on/off), you must regenerate your API tokens to ensure continued access to the API. Existing tokens will become invalid when checksum settings are modified.
Important
When generating or validating a checksum, ensure the payload does not include the
When generating or validating a checksum, ensure the payload does not include the
checksumMethod or checksum fields. These fields should be excluded from checksum computations.Generating Payload Checksum
- Canonicalize Payload - Recursively sort all object keys alphabetically at every nesting level to ensure consistent ordering regardless of how the payload is structured.
- Serialize to JSON - Convert the canonicalized payload to a compact JSON string (no extra whitespace).
- Generate HMAC-SHA256 Hash - The JSON string is hashed using HMAC-SHA256 with the provided secret key.
- Return the Hex Digest - The resulting hash is returned as a 64-character hexadecimal string.
- Recursive sorting ensures that nested objects maintain consistent key ordering at all levels.
- The same checksum is generated regardless of key order in the original payload.
- All data types (strings, numbers, objects, arrays) are properly handled through JSON serialization.
- The checksum is order-independent - the same payload with keys in different orders will produce the same checksum.
Examples
const crypto = require("crypto");
function canonicalize(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(canonicalize);
}
return Object.keys(obj)
.sort()
.reduce((acc, key) => {
acc[key] = canonicalize(obj[key]);
return acc;
}, {});
}
const createPayloadChecksum = (checksumKey, payload) => {
// Canonicalize the payload recursively for consistent ordering
const canonicalPayload = canonicalize(payload);
// Serialize the canonical payload
const payloadString = JSON.stringify(canonicalPayload);
// Create HMAC with SHA256
const hmac = crypto.createHmac("sha256", checksumKey);
hmac.update(payloadString);
return hmac.digest("hex");
};
// Example usage:
const payload = {
amount: 100,
currency: "USD",
reference: "TX123",
exchange: {
fromCurrency: "TZS",
toCurrency: "TZS",
rate: "1",
amount: "1000",
},
customer: {
name: "John Doe",
email: "john@example.com",
phone: "+255123456789"
}
};
const checksumKey = "secret-key";
console.log(createPayloadChecksum(checksumKey, payload));
import json
import hmac
import hashlib
def canonicalize(obj):
if obj is None or not isinstance(obj, (dict, list)):
return obj
if isinstance(obj, list):
return [canonicalize(item) for item in obj]
return {
key: canonicalize(obj[key])
for key in sorted(obj.keys())
}
def create_payload_checksum(checksum_key, payload):
# Canonicalize the payload recursively for consistent ordering
canonical_payload = canonicalize(payload)
# Serialize the canonical payload
payload_string = json.dumps(canonical_payload, separators=(',', ':'), sort_keys=False)
# Create HMAC with SHA256
hmac_obj = hmac.new(
checksum_key.encode('utf-8'),
payload_string.encode('utf-8'),
hashlib.sha256
)
return hmac_obj.hexdigest()
# Example usage:
payload = {
"amount": 100,
"currency": "USD",
"reference": "TX123",
"exchange": {
"fromCurrency": "TZS",
"toCurrency": "TZS",
"rate": "1",
"amount": "1000",
},
"customer": {
"name": "John Doe",
"email": "john@example.com",
"phone": "+255123456789"
}
}
checksum_key = "secret-key"
print(create_payload_checksum(checksum_key, payload))
<?php
function canonicalize($obj) {
if ($obj === null || !is_array($obj)) {
return $obj;
}
// Check if array is a list (sequential numeric keys starting from 0)
if (array_values($obj) === $obj) {
return array_map('canonicalize', $obj);
}
ksort($obj);
$result = [];
foreach ($obj as $key => $value) {
$result[$key] = canonicalize($value);
}
return $result;
}
function createPayloadChecksum($checksumKey, $payload) {
// Canonicalize the payload recursively for consistent ordering
$canonicalPayload = canonicalize($payload);
// Serialize the canonical payload
$payloadString = json_encode($canonicalPayload, JSON_UNESCAPED_SLASHES);
// Create HMAC with SHA256
return hash_hmac('sha256', $payloadString, $checksumKey);
}
// Example usage:
$payload = [
"amount" => 100,
"currency" => "USD",
"reference" => "TX123",
"exchange" => [
"fromCurrency" => "TZS",
"toCurrency" => "TZS",
"rate" => "1",
"amount" => "1000",
],
"customer" => [
"name" => "John Doe",
"email" => "john@example.com",
"phone" => "+255123456789"
]
];
$checksumKey = "secret-key";
echo createPayloadChecksum($checksumKey, $payload);
?>
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sort"
)
func canonicalize(obj interface{}) interface{} {
switch v := obj.(type) {
case nil:
return nil
case map[string]interface{}:
result := make(map[string]interface{})
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
result[k] = canonicalize(v[k])
}
return result
case []interface{}:
result := make([]interface{}, len(v))
for i, item := range v {
result[i] = canonicalize(item)
}
return result
default:
return obj
}
}
func createPayloadChecksum(checksumKey string, payload map[string]interface{}) (string, error) {
// Canonicalize the payload recursively for consistent ordering
canonicalPayload := canonicalize(payload)
// Serialize the canonical payload
payloadBytes, err := json.Marshal(canonicalPayload)
if err != nil {
return "", err
}
// Create HMAC with SHA256
mac := hmac.New(sha256.New, []byte(checksumKey))
mac.Write(payloadBytes)
hashBytes := mac.Sum(nil)
// Convert to hexadecimal string
return hex.EncodeToString(hashBytes), nil
}
// Example usage:
func main() {
payload := map[string]interface{}{
"amount": 100,
"currency": "USD",
"reference": "TX123",
"exchange": map[string]interface{}{
"fromCurrency": "TZS",
"toCurrency": "TZS",
"rate": "1",
"amount": "1000",
},
"customer": map[string]interface{}{
"name": "John Doe",
"email": "john@example.com",
"phone": "+255123456789",
},
}
checksumKey := "secret-key"
checksum, _ := createPayloadChecksum(checksumKey, payload)
fmt.Println(checksum)
}
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
public class Checksum {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.create();
@SuppressWarnings("unchecked")
private static Object canonicalize(Object obj) {
if (obj == null || !(obj instanceof Map)) {
return obj;
}
Map<String, Object> map = (Map<String, Object>) obj;
Map<String, Object> sortedMap = new TreeMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
sortedMap.put(entry.getKey(), canonicalize(entry.getValue()));
}
return sortedMap;
}
public static String createPayloadChecksum(String checksumKey, Map<String, Object> payload)
throws NoSuchAlgorithmException, InvalidKeyException {
// Canonicalize the payload recursively for consistent ordering
Object canonicalPayload = canonicalize(payload);
// Serialize the canonical payload
String payloadString = gson.toJson(canonicalPayload);
// Create HMAC with SHA256
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
checksumKey.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKeySpec);
byte[] hashBytes = mac.doFinal(payloadString.getBytes(StandardCharsets.UTF_8));
// Convert to hexadecimal string
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
// Example usage:
public static void main(String[] args) {
try {
Map<String, Object> payload = new java.util.HashMap<>();
payload.put("amount", 100);
payload.put("currency", "USD");
payload.put("reference", "TX123");
Map<String, Object> exchange = new java.util.HashMap<>();
exchange.put("fromCurrency", "TZS");
exchange.put("toCurrency", "TZS");
exchange.put("rate", "1");
exchange.put("amount", "1000");
payload.put("exchange", exchange);
Map<String, Object> customer = new java.util.HashMap<>();
customer.put("name", "John Doe");
customer.put("email", "john@example.com");
customer.put("phone", "+255123456789");
payload.put("customer", customer);
String checksumKey = "secret-key";
System.out.println(createPayloadChecksum(checksumKey, payload));
} catch (Exception e) {
e.printStackTrace();
}
}
}
Validating Payload Checksum
- Extract Checksum - Extract the checksum from the received request.
-
Prepare Payload - Before recomputing the checksum, exclude the
checksumandchecksumMethodfields from the payload. -
Recompute Checksum - Using the same
createPayloadChecksumfunction, recompute the checksum from the prepared payload using the checksum key. -
Compare the Computed and Received Checksum
- If both checksums match, the payload is valid and untampered.
- If they do not match, reject the request as it may have been modified.
Examples
const crypto = require("crypto");
function canonicalize(obj) {
if (obj === null || typeof obj !== 'object') return obj;
if (Array.isArray(obj)) {
return obj.map(canonicalize);
}
return Object.keys(obj)
.sort()
.reduce((acc, key) => {
acc[key] = canonicalize(obj[key]);
return acc;
}, {});
}
const createPayloadChecksum = (checksumKey, payload) => {
const canonicalPayload = canonicalize(payload);
const payloadString = JSON.stringify(canonicalPayload);
const hmac = crypto.createHmac("sha256", checksumKey);
hmac.update(payloadString);
return hmac.digest("hex");
};
const validateChecksum = (checksumKey, payload, receivedChecksum) => {
// Extract checksumMethod before excluding it from payload
const checksumMethod = payload.checksumMethod || 'canonical';
// Exclude checksum and checksumMethod from payload before validation
const payloadForValidation = { ...payload };
delete payloadForValidation.checksum;
delete payloadForValidation.checksumMethod;
// Pass checksumMethod as option to support legacy method
const computedChecksum = createPayloadChecksum(checksumKey, payloadForValidation, { checksumMethod });
return computedChecksum === receivedChecksum;
};
// Example usage:
const payload = {
amount: 100,
currency: "USD",
reference: "TX123",
exchange: {
fromCurrency: "TZS",
toCurrency: "TZS",
rate: "1",
amount: "1000",
},
customer: {
name: "John Doe",
email: "john@example.com",
phone: "+255123456789"
}
};
const checksumKey = "secret-key";
const receivedChecksum = "some-checksum-from-request"; // Replace with actual received checksum
console.log(validateChecksum(checksumKey, payload, receivedChecksum) ? "Valid" : "Invalid");
import json
import hmac
import hashlib
def canonicalize(obj):
if obj is None or not isinstance(obj, (dict, list)):
return obj
if isinstance(obj, list):
return [canonicalize(item) for item in obj]
return {
key: canonicalize(obj[key])
for key in sorted(obj.keys())
}
def create_payload_checksum(checksum_key, payload):
canonical_payload = canonicalize(payload)
payload_string = json.dumps(canonical_payload, separators=(',', ':'), sort_keys=False)
hmac_obj = hmac.new(
checksum_key.encode('utf-8'),
payload_string.encode('utf-8'),
hashlib.sha256
)
return hmac_obj.hexdigest()
def validate_checksum(checksum_key, payload, received_checksum):
if not received_checksum:
return False
# Extract checksumMethod before excluding it from payload
checksum_method = payload.get('checksumMethod', 'canonical')
# Exclude checksum and checksumMethod from payload before validation
payload_for_validation = {k: v for k, v in payload.items() if k not in ['checksum', 'checksumMethod']}
# Pass checksumMethod as option to support legacy method
computed_checksum = create_payload_checksum(checksum_key, payload_for_validation, {'checksumMethod': checksum_method})
return computed_checksum == received_checksum
# Example usage:
payload = {
"amount": 100,
"currency": "USD",
"reference": "TX123",
"exchange": {
"fromCurrency": "TZS",
"toCurrency": "TZS",
"rate": "1",
"amount": "1000",
},
"customer": {
"name": "John Doe",
"email": "john@example.com",
"phone": "+255123456789"
}
}
checksum_key = "secret-key"
received_checksum = "some-checksum-from-request" # Replace with actual received checksum
print("Valid" if validate_checksum(checksum_key, payload, received_checksum) else "Invalid")
<?php
function canonicalize($obj) {
if ($obj === null || !is_array($obj)) {
return $obj;
}
if (array_values($obj) === $obj) {
return array_map('canonicalize', $obj);
}
ksort($obj);
$result = [];
foreach ($obj as $key => $value) {
$result[$key] = canonicalize($value);
}
return $result;
}
function createPayloadChecksum($checksumKey, $payload) {
$canonicalPayload = canonicalize($payload);
$payloadString = json_encode($canonicalPayload, JSON_UNESCAPED_SLASHES);
return hash_hmac('sha256', $payloadString, $checksumKey);
}
function validateChecksum($checksumKey, $payload, $receivedChecksum) {
if (empty($receivedChecksum)) {
return false;
}
// Extract checksumMethod before excluding it from payload
$checksumMethod = $payload['checksumMethod'] ?? 'canonical';
// Exclude checksum and checksumMethod from payload before validation
$payloadForValidation = $payload;
unset($payloadForValidation['checksum']);
unset($payloadForValidation['checksumMethod']);
// Pass checksumMethod as option to support legacy method
// Note: Your createPayloadChecksum function should accept options parameter
$computedChecksum = createPayloadChecksum($checksumKey, $payloadForValidation, ['checksumMethod' => $checksumMethod]);
return hash_equals($computedChecksum, $receivedChecksum); // Prevent timing attacks
}
// Example usage:
$payload = [
"amount" => 100,
"currency" => "USD",
"reference" => "TX123",
"exchange" => [
"fromCurrency" => "TZS",
"toCurrency" => "TZS",
"rate" => "1",
"amount" => "1000",
],
"customer" => [
"name" => "John Doe",
"email" => "john@example.com",
"phone" => "+255123456789"
]
];
$checksumKey = "secret-key";
$receivedChecksum = "some-checksum-from-request"; // Replace with actual received checksum
if (validateChecksum($checksumKey, $payload, $receivedChecksum)) {
echo "Valid Checksum";
} else {
echo "Invalid Checksum";
}
?>
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"sort"
)
func canonicalize(obj interface{}) interface{} {
switch v := obj.(type) {
case nil:
return nil
case map[string]interface{}:
result := make(map[string]interface{})
keys := make([]string, 0, len(v))
for k := range v {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
result[k] = canonicalize(v[k])
}
return result
case []interface{}:
result := make([]interface{}, len(v))
for i, item := range v {
result[i] = canonicalize(item)
}
return result
default:
return obj
}
}
func createPayloadChecksum(checksumKey string, payload map[string]interface{}) (string, error) {
canonicalPayload := canonicalize(payload)
payloadBytes, err := json.Marshal(canonicalPayload)
if err != nil {
return "", err
}
mac := hmac.New(sha256.New, []byte(checksumKey))
mac.Write(payloadBytes)
hashBytes := mac.Sum(nil)
return hex.EncodeToString(hashBytes), nil
}
func validateChecksum(checksumKey string, payload map[string]interface{}, receivedChecksum string) (bool, error) {
if receivedChecksum == "" {
return false, nil
}
// Extract checksumMethod before excluding it from payload
checksumMethod := "canonical"
if method, ok := payload["checksumMethod"].(string); ok {
checksumMethod = method
}
// Exclude checksum and checksumMethod from payload before validation
payloadForValidation := make(map[string]interface{})
for k, v := range payload {
if k != "checksum" && k != "checksumMethod" {
payloadForValidation[k] = v
}
}
// Pass checksumMethod as option to support legacy method
// Note: Your createPayloadChecksum function should accept options parameter
computedChecksum, err := createPayloadChecksum(checksumKey, payloadForValidation, map[string]interface{}{"checksumMethod": checksumMethod})
if err != nil {
return false, err
}
return computedChecksum == receivedChecksum, nil
}
// Example usage:
func main() {
payload := map[string]interface{}{
"amount": 100,
"currency": "USD",
"reference": "TX123",
"exchange": map[string]interface{}{
"fromCurrency": "TZS",
"toCurrency": "TZS",
"rate": "1",
"amount": "1000",
},
"customer": map[string]interface{}{
"name": "John Doe",
"email": "john@example.com",
"phone": "+255123456789",
},
}
checksumKey := "secret-key"
receivedChecksum := "some-checksum-from-request" // Replace with actual received checksum
isValid, _ := validateChecksum(checksumKey, payload, receivedChecksum)
if isValid {
fmt.Println("Valid Checksum")
} else {
fmt.Println("Invalid Checksum")
}
}
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
public class Checksum {
private static final Gson gson = new GsonBuilder()
.disableHtmlEscaping()
.create();
@SuppressWarnings("unchecked")
private static Object canonicalize(Object obj) {
if (obj == null || !(obj instanceof Map)) {
return obj;
}
Map<String, Object> map = (Map<String, Object>) obj;
Map<String, Object> sortedMap = new TreeMap<>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
sortedMap.put(entry.getKey(), canonicalize(entry.getValue()));
}
return sortedMap;
}
public static String createPayloadChecksum(String checksumKey, Map<String, Object> payload)
throws NoSuchAlgorithmException, InvalidKeyException {
Object canonicalPayload = canonicalize(payload);
String payloadString = gson.toJson(canonicalPayload);
Mac mac = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKeySpec = new SecretKeySpec(
checksumKey.getBytes(StandardCharsets.UTF_8),
"HmacSHA256"
);
mac.init(secretKeySpec);
byte[] hashBytes = mac.doFinal(payloadString.getBytes(StandardCharsets.UTF_8));
StringBuilder hexString = new StringBuilder();
for (byte b : hashBytes) {
String hex = Integer.toHexString(0xff & b);
if (hex.length() == 1) {
hexString.append('0');
}
hexString.append(hex);
}
return hexString.toString();
}
public static boolean validateChecksum(String checksumKey, Map<String, Object> payload, String receivedChecksum)
throws NoSuchAlgorithmException, InvalidKeyException {
// Extract checksumMethod before excluding it from payload
String checksumMethod = (String) payload.getOrDefault("checksumMethod", "canonical");
// Exclude checksum and checksumMethod from payload before validation
Map<String, Object> payloadForValidation = new java.util.HashMap<>(payload);
payloadForValidation.remove("checksum");
payloadForValidation.remove("checksumMethod");
// Pass checksumMethod as option to support legacy method
// Note: Your createPayloadChecksum function should accept options parameter
Map<String, Object> options = new java.util.HashMap<>();
options.put("checksumMethod", checksumMethod);
String computedChecksum = createPayloadChecksum(checksumKey, payloadForValidation, options);
return computedChecksum.equals(receivedChecksum);
}
// Example usage:
public static void main(String[] args) {
try {
Map<String, Object> payload = new java.util.HashMap<>();
payload.put("amount", 100);
payload.put("currency", "USD");
payload.put("reference", "TX123");
Map<String, Object> exchange = new java.util.HashMap<>();
exchange.put("fromCurrency", "TZS");
exchange.put("toCurrency", "TZS");
exchange.put("rate", "1");
exchange.put("amount", "1000");
payload.put("exchange", exchange);
Map<String, Object> customer = new java.util.HashMap<>();
customer.put("name", "John Doe");
customer.put("email", "john@example.com");
customer.put("phone", "+255123456789");
payload.put("customer", customer);
String checksumKey = "secret-key";
String receivedChecksum = "some-checksum-from-request"; // Replace with actual received checksum
if (validateChecksum(checksumKey, payload, receivedChecksum)) {
System.out.println("Valid Checksum");
} else {
System.out.println("Invalid Checksum");
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

