Skip to main content
Webhook signature verification validates that webhook notifications are sent by Cashfree and haven’t been tampered with during transmission. This cryptographic verification protects your server from accepting malicious or forged webhook requests. Each webhook request sent by Cashfree includes the following:
ParameterDescription
x-webhook-signatureHMAC SHA256 signature generated by Cashfree.
x-webhook-timestampTimestamp of when the webhook was generated.
Request bodyThe actual webhook payload.
To validate the webhook, verify the signature using your webhook secret key.

Verification flow

To validate the webhook:
1

Capture headers and raw payload

  • Read x-webhook-signature from request headers.
  • Read x-webhook-timestamp from request headers.
  • Capture the raw request body.
Don’t modify, parse, or reformat the request body before verification.
2

Create signature data

Concatenate the timestamp and raw request body:
signatureData = timestamp + rawBody
3

Generate HMAC SHA256 signature

  • Use your webhook secret key.
  • Apply HMAC SHA256 hashing on signatureData.
4

Encode signature

Encode the hash as a Base64 string.
5

Compare signatures

Compare the computed signature with the received signature:
computed_signature == x-webhook-signature
  • If matched: Webhook is valid.
  • If not matched: Reject the request.

Sample implementations

Select your language or framework to view a sample implementation.
Node.js
const express = require("express");
const crypto = require("crypto");

const app = express();

// Capture raw body
app.use(express.json({
  verify: (req, res, buf) => {
    req.rawBody = buf.toString();
  }
}));

function verifyWebhook(req) {
  const signature = req.headers["x-webhook-signature"];
  const timestamp = req.headers["x-webhook-timestamp"];
  const rawBody = req.rawBody;

  const secretKey = process.env.CASHFREE_WEBHOOK_SECRET;

  const computedSignature = crypto
    .createHmac("sha256", secretKey)
    .update(timestamp + rawBody)
    .digest("base64");

  return computedSignature === signature;
}

app.post("/webhook", (req, res) => {
  if (!verifyWebhook(req)) {
    return res.status(400).send("Invalid signature");
  }

  res.send("Webhook received");
});
Python
import hmac
import hashlib
import base64
from flask import request

def verify_webhook(secret_key):
    webhook_signature = request.headers.get("x-webhook-signature")
    timestamp = request.headers.get("x-webhook-timestamp")

    raw_body = request.get_data(as_text=True)

    signature_data = timestamp + raw_body

    dig = hmac.new(
        secret_key.encode('utf-8'),
        msg=signature_data.encode('utf-8'),
        digestmod=hashlib.sha256
    ).digest()

    computed_signature = base64.b64encode(dig).decode()

    return computed_signature == webhook_signature
Go
import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"io/ioutil"
	"net/http"
)

func verifyWebhook(r *http.Request, secretKey string) bool {
	signature := r.Header.Get("x-webhook-signature")
	timestamp := r.Header.Get("x-webhook-timestamp")

	bodyBytes, _ := ioutil.ReadAll(r.Body)
	rawBody := string(bodyBytes)

	h := hmac.New(sha256.New, []byte(secretKey))
	h.Write([]byte(timestamp + rawBody))
	computed := base64.StdEncoding.EncodeToString(h.Sum(nil))

	return computed == signature
}
PHP
<?php

function verifyWebhook($secretKey) {
    $rawBody = file_get_contents("php://input");
    $timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'];
    $signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];

    $computed = base64_encode(
        hash_hmac('sha256', $timestamp . $rawBody, $secretKey, true)
    );

    return $computed === $signature;
}
?>
Java
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.util.Base64;

public class WebhookValidator {

    public static boolean verify(String rawBody, String timestamp, String signature, String secretKey) {
        try {
            String data = timestamp + rawBody;

            Mac mac = Mac.getInstance("HmacSHA256");
            mac.init(new SecretKeySpec(secretKey.getBytes(), "HmacSHA256"));

            byte[] hash = mac.doFinal(data.getBytes());
            String computed = Base64.getEncoder().encodeToString(hash);

            return computed.equals(signature);

        } catch (Exception e) {
            return false;
        }
    }
}
C#
using System;
using System.Security.Cryptography;
using System.Text;

public class WebhookValidator
{
    public static bool Verify(string rawBody, string timestamp, string signature, string secretKey)
    {
        var data = timestamp + rawBody;

        using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(secretKey)))
        {
            var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(data));
            var computed = Convert.ToBase64String(hash);

            return computed == signature;
        }
    }
}

Security requirements

Keep the following requirements in mind when implementing webhook signature verification.
  • Always use the raw request body for signature computation.
  • Don’t modify, parse, or reformat the payload before verification.
  • Store your webhook secret key securely. Use environment variables.
  • Reject webhook requests if signature mismatch occurs or required headers are missing.

Best practices

Use these practices to improve the security and reliability of your webhook implementation.
  • Validate timestamp to prevent replay attacks (recommended window: 5 minutes).
  • Log invalid webhook attempts for debugging and monitoring.
  • Process webhook events only after successful signature verification.

Summary

The verification process covers the following steps:
  1. Capture the x-webhook-signature and x-webhook-timestamp headers along with the raw request body.
  2. Concatenate the timestamp and raw body to form the signature data.
  3. Generate an HMAC SHA256 hash using your webhook secret key.
  4. Encode the hash as a Base64 string.
  5. Compare the computed signature with x-webhook-signature. If they match, the webhook is valid. If they don’t match, reject the request.