> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clickpesa.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Checksum

> Generate checksum for secure transactions via API

The payloads checksum ensures data integrity and consistency during API requests.

On incoming requests it ensures that the payment or payout request is from a valid merchant and that no one has tampered with it.

[Webhooks](/home/webhooks) payload are also signed using the same method as defined below. This allows merchants to ensure that the webhook request came from ClickPesa and that no one has tampered with it.

<Warning>
  **Important** <br /> When you change your checksum settings (turning on/off), you must [regenerate your API tokens](/api-reference/authorization/generate-token) to ensure continued access to the API. Existing tokens will become invalid when checksum settings are modified.
</Warning>

<Warning>
  **Important** <br />  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.
</Warning>

## **Generating Payload Checksum**

1. **Canonicalize Payload** - Recursively sort all object keys alphabetically at every nesting level to ensure consistent ordering regardless of how the payload is structured.
2. **Serialize to JSON** - Convert the canonicalized payload to a compact JSON string (no extra whitespace).
3. **Generate HMAC-SHA256 Hash** - The JSON string is hashed using HMAC-SHA256 with the provided secret key.
4. **Return the Hex Digest** - The resulting hash is returned as a 64-character hexadecimal string.

<Note>
  * **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.
</Note>

### **Examples**

<CodeGroup>
  ```javascript Javascript theme={null}
  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));
  ```

  ```python Python theme={null}
  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 PHP theme={null}
  <?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);
  ?>
  ```

  ```go Go theme={null}
  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)
  }
  ```

  ```java Java theme={null}
  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();
          }
      }
  }
  ```
</CodeGroup>

## Validating Payload Checksum

1. **Extract Checksum**  - Extract the checksum from the received request.

2. **Prepare Payload** - Before recomputing the checksum, **exclude** the `checksum` and `checksumMethod` fields from the payload.

3. **Recompute Checksum** - Using the same `createPayloadChecksum` function, recompute the checksum from the prepared payload using the checksum key.

4. **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**

<CodeGroup>
  ```javascript Javascript theme={null}
  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");
  ```

  ```python Python theme={null}
  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 PHP theme={null}
  <?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";
  }
  ?>
  ```

  ```go Go theme={null}
  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")
  	}
  }
  ```

  ```java Java theme={null}
  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();
          }
      }
  }
  ```
</CodeGroup>

## **Demo**

[Checksum Demo Repository](https://github.com/ClickPesa/clickpesa-api-checksum-demo) - includes implementations in JavaScript, Python, PHP, Java, and Go, all verified for cross-language interoperability.
