Cashfree generates the webhook signature based on the raw payload, not the
parsed payload. You can refer to how the popular JavaScript framework
NestJS provides a hook for accessing
the raw body.
- The timestamp is present in the header
x-webhook-timestamp. - The actual signature is present in the header
x-webhook-signature.
signature-verification
timestamp := 1617695238078;
signedPayload := $timestamp.$payload;
expectedSignature := Base64Encode(HMACSHA256($signedPayload, $merchantSecretKey));
SDK verification (built-in approach)
var express = require('express')
const { Cashfree, CFEnvironment } = require "cashfree-pg";
var app = express()
const cashfree = new Cashfree(
CFEnvironment.SANDBOX,
"{Client ID}",
"{Client Secret Key}"
);
app.post('/webhook', function (req, res) {
try {
cashfree.PGVerifyWebhookSignature(req.headers["x-webhook-signature"], req.rawBody, req.headers["x-webhook-timestamp"])
} catch (err) {
console.log(err.message)
}
})
import (
cashfree "github.com/cashfree/cashfree-pg/v4"
)
// Route
e.POST("/webhook", _this.Webhook)
// Controller
func (_this *WebhookRoute) Webhook(c echo.Context) error {
clientId := "<x-client-id>"
clientSecret := "<x-client-secret>"
cashfree.XClientId = &clientId
cashfree.XClientSecret = &clientSecret
cashfree.XEnvironment = cashfree.SANDBOX
signature := c.Request().Header.Get("x-webhook-signature")
timestamp := c.Request().Header.Get("x-webhook-timestamp")
body, _ := ioutil.ReadAll(c.Request().Body)
rawBody := string(body)
webhookEvent, err := cashfree.PGVerifyWebhookSignature(signature, rawBody, timestamp)
if err != nil {
fmt.Println(err.Error())
} else {
fmt.Println(webhookEvent.Object)
}
}
<?php
$inputJSON = file_get_contents('php://input');
$expectedSig = getallheaders()['x-webhook-signature'];
$ts = getallheaders()['x-webhook-timestamp'];
if(!isset($expectedSig) || !isset($ts)){
echo "Bad Request";
die();
}
\Cashfree\Cashfree::$XClientId = "<x-client-id>";
\Cashfree\Cashfree::$XClientSecret = "<x-client-secret>";
$cashfree = new \Cashfree\Cashfree();
try {
$response = cashfree->PGVerifyWebhookSignature($expectedSig, $inputJSON, $ts);
} catch(Exception $e) {
// Error if signature verification fails
}
?>
from cashfree_pg.api_client import Cashfree
@app.route('/webhook', methods = ['POST'])
def disp():
# Get the raw body from the request
raw_body = request.data
# Decode the raw body bytes into a string
decoded_body = raw_body.decode('utf-8')
#verify_signature
timestamp = request.headers['x-webhook-timestamp']
signature = request.headers['x-webhook-signature']
cashfree = Cashfree()
cashfree.XClientId = "<app_id>"
cashfree.XClientSecret = "<secret_key>"
try:
cashfreeWebhookResponse = cashfree.PGVerifyWebhookSignature(signature, decoded_body, timestamp)
except:
# If Signature mis-match
import com.cashfree.*;
@PostMapping("/my-endpoint")
public String handlePost(HttpServletRequest request) throws IOException {
Cashfree.XClientId = "<x-client-id>";
Cashfree.XClientSecret = "<x-client-secret>";
Cashfree.XEnvironment = Cashfree.SANDBOX;
StringBuilder stringBuilder = new StringBuilder();
BufferedReader bufferedReader = null;
try {
bufferedReader = request.getReader();
String line;
while ((line = bufferedReader.readLine()) != null) {
stringBuilder.append(line).append('\n');
}
String rawBody = stringBuilder.toString();
String signature = request.getHeader("x-webhook-signature");
String timestamp = request.getHeader("x-webhook-timestamp");
Cashfree cashfree = new Cashfree();
PGWebhookEvent webhook = cashfree.PGVerifyWebhookSignature(signature, rawBody, timestamp);
} catch (Exception e) {
// Error if verification fails
} finally {
if (bufferedReader != null) {
bufferedReader.close();
}
}
}
using cashfree_pg.Client;
using cashfree_pg.Model;
[Route("api/[controller]")]
[ApiController]
public class YourController : ControllerBase
{
[HttpPost]
public async Task<IActionResult> Post()
{
// Read the raw body of the POST request
using (StreamReader reader = new StreamReader(Request.Body, Encoding.UTF8))
{
string requestBody = await reader.ReadToEndAsync();
var headers = Request.Headers;
var signature = headers["x-webhook-signature"];
var timestamp = headers["x-webhook-timestamp"];
Cashfree.XClientId = "<x-client-id>";
Cashfree.XClientSecret = "<x-client-secret>";
Cashfree.XEnvironment = Cashfree.SANDBOX;
var cashfree = new Cashfree();
try {
var response = cashfree.PGVerifyWebhookSignature(signature, requestBody, timestamp);
} catch(Exception e) {
// Error if signature mis matches
}
}
}
}
Manual verification (custom approach)
function verify(ts, request){
const body = request.headers['x-webhook-timestamp'] + request.rawBody
const secretKey = <client secret>;
let generatedSignature = crypto.createHmac('sha256', secretKey).update(body).digest("base64");
const signature = request.headers['x-webhook-signature']
if(generatedSignature === signature) {
let jsonObject = JSON.parse(rawBody)
return jsonObject
}
throw new Error("Generated signature and received signature did not match.");
}
func VerifySignature(expectedSig string, ts string, body string) (string, error) {
clientSecret := "<client-secret>"
signatureString := ts + rawBody
hmacInstance := hmac.New(sha256.New, []byte(*clientSecret))
hmacInstance.Write([]byte(signatureString))
bytesData := hmacInstance.Sum(nil)
generatedSignature := base64.StdEncoding.EncodeToString(bytesData)
if generatedSignature == expectedSig {
var object interface{}
err := json.Unmarshal([]byte(rawBody), &object)
if err != nil {
return nil, errors.New("something went wrong when unmarshalling raw body")
}
if objectAsMapInterface, ok := object.(map[string]interface{}); ok {
if webhookType, ok := objectAsMapInterface["type"].(string); ok {
return "signatures match", nil
}
}
return "", nil
}
return nil, errors.New("generated signature and received signature did not match")
}
timestamp := c.Request().Header.Get("x-webhook-timestamp")
body, _ := ioutil.ReadAll(c.Request().Body)
rawBody := string(body)
signature := c.Request().Header.Get("x-webhook-signature")
VerifySignature(signature, timestamp, rawBody)
function computeSignature($ts, $rawBody){
$rawBody = file_get_contents('php://input');
$timestamp = getallheaders()['x-webhook-timestamp'];
$signature = getallheaders()['x-webhook-signature'];
$body = $timestamp . $rawBody;
$secretKey = "<client-secret>";
$genSignature = hash_hmac('sha256', $body, $secretKey, true);
$genSignatureBase64 = base64_encode($genSignature);
if($genSignatureBase64 == $signature) {
$jsonResponse = json_decode($rawBody);
return $jsonResponse;
}
throw new Exception("Generated signature and received signature did not match.");
}
public Object generateSignature() {
try {
String data = headers.get("x-webhook-timestamp") + rawBody;
String secretKey = "<client-secret>";
Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
SecretKeySpec secret_key_spec = new SecretKeySpec(secretKey.getBytes(), "HmacSHA256");
sha256_HMAC.init(secret_key_spec);
String computed_signature = Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(data.getBytes()));
String signature = headers.get("x-webhook-signature")
if(computed_signature.equals(signature)) {
Gson g = new Gson();
Object response = g.fromJson(rawBody, Object.class);
return response;
}
throw new Exception("Generated signature and received signature did not match.");
} catch (Exception e) {
throw e;
}
}
import base64
import hashlib
import hmac
def generateSignature():
raw_body = request.data
timestamp = request.headers['x-webhook-timestamp']
signature = request.headers['x-webhook-signature']
signatureData = timestamp+rawBody
message = bytes(signatureData, 'utf-8')
secretkey=bytes("<client-signature>",'utf-8')
generatedSignature = base64.b64encode(hmac.new(secretkey, message, digestmod=hashlib.sha256).digest())
computed_signature = str(generatedSignature, encoding='utf8')
if computed_signature == signature:
json_response = json.loads(rawBody)
return json_response
raise Exception("Generated signature and received signature did not match.")