Callback Signature Validation

Secure Integration Steps for Merchant Authentication

TezPay Callback Signature Validation

Overview

TezPay sends callbacks to merchants with HMAC-SHA256 signatures to ensure data integrity and authenticity. This document explains how to validate these signatures in your merchant application.

Callback Format

TezPay sends callbacks with the following structure:
{ "tx_id": "c8e092a1-658a-4216-8747-abedca22ff6a", "status": "COMPLETED", "updated_at": "2025-09-19T19:25:34.015277+00:00", "merchant_reference": "PAY4653613844", "payment_method": "IMPS_IN", "signature": "a1b2c3d4e5f6789..." }

Signature Generation Process

The signature is generated using HMAC-SHA256 with the following process:
  1. Extract fields in this exact order:
      • tx_id
      • status
      • merchant_reference
      • updated_at
      • payment_method
  1. Concatenate fields without any separators:
    1. message = tx_id + status + merchant_reference + updated_at + payment_method
  1. Generate HMAC-SHA256 using your client_secret:
    1. signature = HMAC-SHA256(message, client_secret)

Implementation Examples

JavaScript/Node.js

const crypto = require('crypto'); function generateSignature(callbackData, clientSecret) { const message = callbackData.tx_id + callbackData.status + callbackData.merchant_reference + callbackData.updated_at + callbackData.payment_method; return crypto.createHmac('sha256', clientSecret) .update(message) .digest('hex'); } function validateSignature(callbackData, receivedSignature, clientSecret) { const expectedSignature = generateSignature(callbackData, clientSecret); return crypto.timingSafeEqual( Buffer.from(expectedSignature, 'hex'), Buffer.from(receivedSignature, 'hex') ); } // Usage const callbackData = { tx_id: "c8e092a1-658a-4216-8747-abedca22ff6a", status: "COMPLETED", merchant_reference: "PAY4653613844", updated_at: "2025-09-19T19:25:34.015277+00:00", payment_method: "IMPS_IN" }; const clientSecret = "AtHhxl0NIBImMMb4l4ToPf2lfurWDyM9SMe26sWPJWM"; const receivedSignature = "a1b2c3d4e5f6789..."; const isValid = validateSignature(callbackData, receivedSignature, clientSecret); console.log("Signature valid:", isValid);

PHP

<?php function generateSignature($callbackData, $clientSecret) { $message = $callbackData['tx_id'] . $callbackData['status'] . $callbackData['merchant_reference'] . $callbackData['updated_at'] . $callbackData['payment_method']; return hash_hmac('sha256', $message, $clientSecret); } function validateSignature($callbackData, $receivedSignature, $clientSecret) { $expectedSignature = generateSignature($callbackData, $clientSecret); return hash_equals($expectedSignature, $receivedSignature); } // Usage $callbackData = [ 'tx_id' => 'c8e092a1-658a-4216-8747-abedca22ff6a', 'status' => 'COMPLETED', 'merchant_reference' => 'PAY4653613844', 'updated_at' => '2025-09-19T19:25:34.015277+00:00', 'payment_method' => 'IMPS_IN' ]; $clientSecret = 'AtHhxl0NIBImMMb4l4ToPf2lfurWDyM9SMe26sWPJWM'; $receivedSignature = 'a1b2c3d4e5f6789...'; $isValid = validateSignature($callbackData, $receivedSignature, $clientSecret); echo "Signature valid: " . ($isValid ? 'true' : 'false'); ?>

Python

import hmac import hashlib def generate_signature(callback_data, client_secret): message = (callback_data['tx_id'] + callback_data['status'] + callback_data['merchant_reference'] + callback_data['updated_at'] + callback_data['payment_method']) return hmac.new( client_secret.encode('utf-8'), message.encode('utf-8'), hashlib.sha256 ).hexdigest() def validate_signature(callback_data, received_signature, client_secret): expected_signature = generate_signature(callback_data, client_secret) return hmac.compare_digest(expected_signature, received_signature) # Usage callback_data = { 'tx_id': 'c8e092a1-658a-4216-8747-abedca22ff6a', 'status': 'COMPLETED', 'merchant_reference': 'PAY4653613844', 'updated_at': '2025-09-19T19:25:34.015277+00:00', 'payment_method': 'IMPS_IN' } client_secret = 'AtHhxl0NIBImMMb4l4ToPf2lfurWDyM9SMe26sWPJWM' received_signature = 'a1b2c3d4e5f6789...' is_valid = validate_signature(callback_data, received_signature, client_secret) print(f"Signature valid: {is_valid}")

Java

import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import java.nio.charset.StandardCharsets; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; public class SignatureValidator { public static String generateSignature(String txId, String status, String merchantReference, String updatedAt, String paymentMethod, String clientSecret) throws NoSuchAlgorithmException, InvalidKeyException { String message = txId + status + merchantReference + updatedAt + paymentMethod; Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec secretKey = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); mac.init(secretKey); byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8)); return bytesToHex(hash); } public static boolean validateSignature(String txId, String status, String merchantReference, String updatedAt, String paymentMethod, String receivedSignature, String clientSecret) throws NoSuchAlgorithmException, InvalidKeyException { String expectedSignature = generateSignature(txId, status, merchantReference, updatedAt, paymentMethod, clientSecret); return expectedSignature.equals(receivedSignature); } private static String bytesToHex(byte[] bytes) { StringBuilder result = new StringBuilder(); for (byte b : bytes) { result.append(String.format("%02x", b)); } return result.toString(); } }

C

using System; using System.Security.Cryptography; using System.Text; public class SignatureValidator { public static string GenerateSignature(string txId, string status, string merchantReference, string updatedAt, string paymentMethod, string clientSecret) { string message = txId + status + merchantReference + updatedAt + paymentMethod; byte[] keyBytes = Encoding.UTF8.GetBytes(clientSecret); byte[] messageBytes = Encoding.UTF8.GetBytes(message); using (var hmac = new HMACSHA256(keyBytes)) { byte[] hash = hmac.ComputeHash(messageBytes); return BitConverter.ToString(hash).Replace("-", "").ToLower(); } } public static bool ValidateSignature(string txId, string status, string merchantReference, string updatedAt, string paymentMethod, string receivedSignature, string clientSecret) { string expectedSignature = GenerateSignature(txId, status, merchantReference, updatedAt, paymentMethod, clientSecret); return expectedSignature.Equals(receivedSignature, StringComparison.OrdinalIgnoreCase); } }

Security Best Practices

  1. Always validate signatures before processing callbacks
  1. Use secure comparison (timing-safe) to prevent timing attacks
  1. Store client_secret securely (environment variables, secure vaults)
  1. Log validation results for debugging and monitoring
  1. Reject invalid signatures in production environments

Testing

You can test your implementation using the provided mock credentials:
{ "client_id": "502034de-48e4-44eb-a437-2bc7773964f7", "client_secret": "AtHhxl0NIBImMMb4l4ToPf2lfurWDyM9SMe26sWPJWM" }

Support

For technical support or questions about signature validation, please contact the TezPay support team.