> ## Documentation Index
> Fetch the complete documentation index at: https://www.cashfree.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Signature Verification

> Verify Cashfree Subscriptions webhook signatures to confirm authenticity, prevent spoofing, and validate payload integrity before processing recurring events.

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:

| Parameter             | Description                                  |
| --------------------- | -------------------------------------------- |
| `x-webhook-signature` | HMAC SHA256 signature generated by Cashfree. |
| `x-webhook-timestamp` | Timestamp of when the webhook was generated. |
| Request body          | The actual webhook payload.                  |

To validate the webhook, verify the signature using your webhook secret key.

## Verification flow

To validate the webhook:

<Steps>
  <Step title="Capture headers and raw payload">
    * Read `x-webhook-signature` from request headers.
    * Read `x-webhook-timestamp` from request headers.
    * Capture the raw request body.

    <Warning>
      Don't modify, parse, or reformat the request body before verification.
    </Warning>
  </Step>

  <Step title="Create signature data">
    Concatenate the timestamp and raw request body:

    ```text theme={"dark"}
    signatureData = timestamp + rawBody
    ```
  </Step>

  <Step title="Generate HMAC SHA256 signature">
    * Use your webhook secret key.
    * Apply HMAC SHA256 hashing on `signatureData`.
  </Step>

  <Step title="Encode signature">
    Encode the hash as a Base64 string.
  </Step>

  <Step title="Compare signatures">
    Compare the computed signature with the received signature:

    ```text theme={"dark"}
    computed_signature == x-webhook-signature
    ```

    * If matched: Webhook is valid.
    * If not matched: Reject the request.
  </Step>
</Steps>

## Sample implementations

Select your language or framework to view a sample implementation.

<Accordion title="Node.js (Express)">
  ```javascript Node.js theme={"dark"}
  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");
  });
  ```
</Accordion>

<Accordion title="Python (Flask)">
  ```python Python theme={"dark"}
  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
  ```
</Accordion>

<Accordion title="Go">
  ```go Go theme={"dark"}
  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
  }
  ```
</Accordion>

<Accordion title="PHP">
  ```php PHP theme={"dark"}
  <?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;
  }
  ?>
  ```
</Accordion>

<Accordion title="Java">
  ```java Java theme={"dark"}
  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;
          }
      }
  }
  ```
</Accordion>

<Accordion title="C# (.NET)">
  ```csharp C# theme={"dark"}
  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;
          }
      }
  }
  ```
</Accordion>

## 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.
