Skip to main content
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 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.
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.
Important
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

  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.
  • 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: "[email protected]",
    phone: "+255123456789"
  }
};
const checksumKey = "secret-key";
console.log(createPayloadChecksum(checksumKey, payload));

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

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: "[email protected]",
    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");

Demo

Checksum Demo Repository - includes implementations in JavaScript, Python, PHP, Java, and Go, all verified for cross-language interoperability.