⚙️
GovNet Gateway API Documentation
  • Introduction
  • Getting Started
    • Registration
    • Error Handling
    • Authentication
    • Merchant Account Credentials
      • Generate Secret Key
      • Generate New Keys
    • Supported Countries
    • Sandbox Test Accounts
  • Utility Functions
    • Balance Inquiry
    • Payment Options
    • Handling Notifications/Callbacks
      • Callback Events
    • Handling Card Redirects
  • Collections
    • Getting Started
    • Request Mobile Money Payment
    • Request Card Payment
    • Collect For Payment
  • Callbacks & Redirects
    • HMAC Signature Verification
    • RSA Signature Verification
Powered by GitBook
On this page
  • Obtain the Signing Key
  • Next Steps
  1. Callbacks & Redirects

HMAC Signature Verification

The section describes how the hmac signature sent in the callback header OR in the redirect data can be verified

PreviousCollect For PaymentNextRSA Signature Verification

Last updated 11 months ago

Obtain the Signing Key

The signing key is an alpha-numeric string generated by our platform during your merchant account creation and it is stored against your account record. This value can be found under you account details in the merchant dashboard. GovBill uses this value to create the hmac signature and the same will be used when verifying the signature. It is recommended that it is copied and stored safely together with the security keys.

Below is the sample callback data to be used for the demonstration (sample redirect data is available );

{
    "event": "transaction.failed",
    "payload": {
        "id": 708,
        "merchant_reference": "MCTREFYDPE9LMZ34S8HM",
        "internal_reference": "GOVBILGHQ6ZDXFK7C7NJ",
        "transaction_type": "COLLECTION",
        "request_currency": "UGX",
        "request_amount": 35000,
        "transaction_currency": "UGX",
        "transaction_amount": 35000,
        "transaction_charge": 0,
        "charge_customer": false,
        "provider_code": "mtn_momo_ug",
        "transaction_status": "FAILED",
        "status_message": "Balance Insufficient for the transaction",
        "transaction_account": "256772000002",
        "customer_name": "JOHN DOE",
        "institution_name": "MTN",
        "total_credit": 0
    }
}

Next Steps

  1. Obtain the value of the hmac-signature header (if callback) OR the value of the hmac_signature query parameter (if redirect). The value sent in the signature header takes the format t=timestamp,s=hmac_hash

  2. Form the string payload to be used in signature verification. This is obtained by concatenating values of the callback/redirect data in the format; event:merchant_reference:internal_reference:transaction_type:transaction_status and these values are obtained from the callback/redirect data. The string payload in this case would therefore be transaction.failed:MCTREFYDPE9LMZ34S8HM:GOVBILGHQ6ZDXFK7C7NJ:COLLECTION:FAILED

  3. Create the hmac hash of the string payload.

  4. Compare the resulting hash to the value in the hmac-signature header. Equality means the signature is valid.

<?php

public function isValidSignature() {
    $strPayload = "transaction.failed:MCTREFYDPE9LMZ34S8HM:GOVBILGHQ6ZDXFK7C7NJ:COLLECTION:FAILED";
    $signingKey = "your signing key string";
    $hmacSignature = "value of hmac-signature header";

    try {
      $timestamp = null;
      $hmacHash = null;

      // Split the hmacSignature into key-value pairs
      foreach (explode(",", $hmacSignature) as $sig_part) {
        [$key, $value] = explode("=", $sig_part);
        switch ($key) {
          case "t":
            $timestamp = $value;
            break;
          case "s":
            $hmacHash = $value;
            break;
        }
      }

      // Optional timestamp check based on your logic

      // Calculate the HMAC signature
      $signature = hash_hmac("sha256", $strPayload, $signingKey, false);

      // Compare the calculated and provided signatures
      return $signature === $hmacHash;
    } catch (Exception $e) {
      return false;
    }
}

?>
const crypto = require('crypto');

function isValidSignature() {
    const strPayload = "transaction.failed:MCTREFYDPE9LMZ34S8HM:GOVBILGHQ6ZDXFK7C7NJ:COLLECTION:FAILED";
    const signingKey = "your signing key string";
    const hmacSignature = "value of hmac-signature header";

    try {
        let timestamp, hmacHash;
        for (const sig_part of hmacSignature.split(",")) {
            const [key, value] = sig_part.split("=");
            switch (key) {
                case "t":
                    timestamp = value;
                    break;
                case "s":
                    hmacHash = value;
                    break;
            }
        }

        /*you can optionally check the timestamp. your current timestamp and the timestamp in the header should not be more than 30 seconds apart*/

        const signature = crypto.createHmac("sha256", signingKey).update(strPayload).digest("hex");
        /*true or false*/
        return signature === hmacHash;
    } catch (e) {
        return false;
    }
}
import java.io.UnsupportedEncodingException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.HashMap;
import java.util.Map;

public class SignatureValidator {

  private static final String HMAC_SHA256_ALGORITHM = "HmacSHA256";

  public static boolean isValidSignature(String signingKey, String strPayload, String hmacSignature)
      throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
    // Extract timestamp and signature hash from hmacSignature
    Map<String, String> signatureParts = parseSignatureParts(hmacSignature);
    String timestamp = signatureParts.get("t");
    String hmacHash = signatureParts.get("s");

    // Optional timestamp check based on your logic

    // Calculate the HMAC signature
    byte[] calculatedSignature = calculateHmac(signingKey, strPayload);
    String encodedSignature = Base64.getEncoder().encodeToString(calculatedSignature);

    // Compare the calculated and provided signatures
    return encodedSignature.equals(hmacHash);
  }

  private static Map<String, String> parseSignatureParts(String hmacSignature) {
    Map<String, String> parts = new HashMap<>();
    for (String sigPart : hmacSignature.split(",")) {
      String[] keyValue = sigPart.split("=");
      parts.put(keyValue[0], keyValue[1]);
    }
    return parts;
  }

  private static byte[] calculateHmac(String signingKey, String strPayload)
      throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
    byte[] decodedKey = Base64.getDecoder().decode(signingKey);
    Mac mac = Mac.getInstance(HMAC_SHA256_ALGORITHM);
    mac.init(new SecretKeySpec(decodedKey, HMAC_SHA256_ALGORITHM));
    return mac.doFinal(strPayload.getBytes("UTF-8"));
  }

  public static void main(String[] args) throws UnsupportedEncodingException, NoSuchAlgorithmException, InvalidKeyException {
    // Replace these values with your actual data
    String signingKey = "your_signing_key";
    String strPayload = "transaction.failed:MCTREFYDPE9LMZ34S8HM:GOVBILGHQ6ZDXFK7C7NJ:COLLECTION:FAILED";
    String hmacSignature = "value of hmac-signature header";

    // Call the isValidSignature method and print the result
    boolean isValid = isValidSignature(signingKey, strPayload, hmacSignature);
    System.out.println("Signature is valid: " + isValid);
  }
}
using System;
using System.Collections.Generic;
using System.Security.Cryptography;
using System.Text;

public class SignatureValidator
{
    private const string HmacSha256Algorithm = "HmacSha256";

    public static bool IsValidSignature(string signingKey, string strPayload, string hmacSignature)
    {
        try
        {
            // Extract timestamp and signature hash (optional timestamp check not included)
            var signatureParts = SplitAndParseSignatureParts(hmacSignature);
            var hmacHash = signatureParts["s"];

            // Calculate the HMAC signature
            var calculatedSignature = CalculateHmac(signingKey, strPayload);
            var encodedSignature = Convert.ToBase64String(calculatedSignature);

            // Compare the calculated and provided signatures
            return encodedSignature.Equals(hmacHash);
        }
        catch (Exception ex)
        {
            return false;
        }
    }

    private static Dictionary<string, string> SplitAndParseSignatureParts(string hmacSignature)
    {
        var parts = new Dictionary<string, string>();
        foreach (var sigPart in hmacSignature.Split(','))
        {
            var keyValue = sigPart.Split('=');
            parts.Add(keyValue[0], keyValue[1]);
        }
        return parts;
    }

    private static byte[] CalculateHmac(string signingKey, string strPayload)
    {
        using (var hmac = HMACSHA256.Create())
        {
            hmac.Key = Convert.FromBase64String(signingKey);
            return hmac.ComputeHash(Encoding.UTF8.GetBytes(strPayload));
        }
    }

    public static void Main(string[] args)
    {
        // Replace these values with your actual data
        string signingKey = "your_signing_key";
        string strPayload = "transaction.failed:MCTREFYDPE9LMZ34S8HM:GOVBILGHQ6ZDXFK7C7NJ:COLLECTION:FAILED";
        string hmacSignature = "value of hmac-signature header";

        // Call the isValidSignature method and print the result
        bool isValid = IsValidSignature(signingKey, strPayload, hmacSignature);
        Console.WriteLine("Signature is valid: {0}", isValid);
    }
}
import base64
import hmac
import hashlib

def is_valid_signature(signing_key, str_payload, hmac_signature):
    try:
        # Extract timestamp and signature hash (optional timestamp check not included)
        signature_parts = {key: value for key, value in map(lambda x: x.split("="), hmac_signature.split(","))}
        hmac_hash = signature_parts["s"]

        # Calculate the HMAC signature
        calculated_signature = hmac.new(signing_key.encode(), str_payload.encode(), hashlib.sha256).hexdigest()

        # Compare the calculated and provided signatures
        return calculated_signature == hmac_hash
    except Exception:
        return False

# Replace these values with your actual data
signing_key = "your_signing_key"
str_payload = "transaction.failed:MCTREFYDPE9LMZ34S8HM:GOVBILGHQ6ZDXFK7C7NJ:COLLECTION:FAILED"
hmac_signature = "value of hmac-signature header"

# Call the is_valid_signature function and print the result
is_valid = is_valid_signature(signing_key, str_payload, hmac_signature)
print("Signature is valid:", is_valid)
require 'base64'
require 'digest/hmac'

def is_valid_signature?(signing_key, str_payload, hmac_signature)
  begin
    # Extract timestamp and signature hash (optional timestamp check not included)
    signature_parts = hmac_signature.split(",").map { |part| part.split("=") }.to_h
    hmac_hash = signature_parts["s"]

    # Calculate the HMAC signature
    calculated_signature = Base64.encode64(OpenSSL::HMAC.digest(OpenSSL::Digest::SHA256.new, signing_key, str_payload))

    # Compare the calculated and provided signatures
    return calculated_signature == hmac_hash
  rescue StandardError
    return false

Below is a sample signature

t=1708085942865,s=c51830dfe748c06b14800ba2fbc8415730710edfdac4f5ca20ba9da2f42
here