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

# Payloads and Signatures

> Understand Cashfree webhook JSON structure, sample payloads, and how to verify HMAC signatures for Payment Gateway, Payouts, Secure ID, and PPI.

This page covers the signature verification algorithm you must implement before processing any webhook, and sample payloads for common event types. If you haven't registered a webhook URL yet, start with [Configure webhook endpoints](/docs/api-reference/webhooks/configure-endpoints).

## Signature verification

<Warning>
  Verify the signature on every incoming webhook request before you trust or act on its payload. Skipping verification allows forged requests to trigger business logic and fraudulent event injection.
</Warning>

Payment Gateway, Payouts V2, Secure ID, and PPI all use the same HMAC-SHA256 header-based algorithm. Payouts V1 (Cashgram) uses form-encoded POST parameters.

### Header-based verification

Applies to Payment Gateway, Payouts V2, Secure ID, and PPI.

The signature is in the `x-webhook-signature` header. The timestamp used in the signature is in `x-webhook-timestamp`.

**Algorithm:**

```text theme={"dark"}
signedPayload     = x-webhook-timestamp + rawBody
expectedSignature = Base64Encode(HMAC-SHA256(signedPayload, clientSecret))
```

<Warning>
  Always compute the signature from the raw request body string. Parsing and re-serialising JSON can change whitespace, field order, or number formatting and will cause verification to fail.
</Warning>

<Tip>
  Use the official Cashfree SDK where available. It captures the raw body and performs signature verification in a single method call, reducing the risk of implementation errors.
</Tip>

<CodeGroup>
  ```javascript Node.js (SDK) theme={"dark"}
  const { Cashfree, CFEnvironment } = require("cashfree-pg");

  const cashfree = new Cashfree(
    CFEnvironment.PRODUCTION,
    "<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"]
      );
      res.sendStatus(200);
    } catch (err) {
      console.error("Signature verification failed:", err.message);
      res.sendStatus(400);
    }
  });
  ```

  ```javascript Node.js (manual) theme={"dark"}
  const crypto = require("crypto");

  function verifySignature(req) {
    const body = req.headers["x-webhook-timestamp"] + req.rawBody;
    const secret = process.env.CASHFREE_CLIENT_SECRET;

    const generatedSignature = crypto
      .createHmac("sha256", secret)
      .update(body)
      .digest("base64");

    const receivedSignature = req.headers["x-webhook-signature"];

    if (generatedSignature !== receivedSignature) {
      throw new Error("Signature mismatch. Request rejected.");
    }

    return JSON.parse(req.rawBody);
  }
  ```

  ```go Go theme={"dark"}
  func VerifyWebhookSignature(
    signature string,
    rawBody   string,
    timestamp string,
  ) error {
    signatureString := timestamp + rawBody
    h := hmac.New(sha256.New, []byte(*XClientSecret))
    h.Write([]byte(signatureString))
    generatedSignature := base64.StdEncoding.EncodeToString(h.Sum(nil))

    if generatedSignature != signature {
      return errors.New("generated signature and received signature did not match")
    }
    return nil
  }
  ```

  ```java Java theme={"dark"}
  import javax.crypto.Mac;
  import javax.crypto.spec.SecretKeySpec;
  import java.util.Base64;

  public String generateSignature(HttpServletRequest request) throws Exception {
      BufferedReader bufferedReader = request.getReader();
      StringBuilder stringBuilder = new StringBuilder();
      String line;
      while ((line = bufferedReader.readLine()) != null) {
          stringBuilder.append(line);
      }
      String payload = stringBuilder.toString();
      String timestamp = request.getHeader("x-webhook-timestamp");

      String data = timestamp + payload;
      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);

      return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(data.getBytes()));
  }
  ```

  ```php PHP theme={"dark"}
  function verifySignature() {
      $rawBody = file_get_contents('php://input');
      $ts = getallheaders()['x-webhook-timestamp'];

      $signStr = $ts . $rawBody;
      $key = "<client-secret>";
      $computedSig = base64_encode(hash_hmac('sha256', $signStr, $key, true));

      return $computedSig; // compare with x-webhook-signature header value
  }
  ```

  ```python Python theme={"dark"}
  import base64
  import hashlib
  import hmac

  def generate_signature(request):
      raw_body = request.data.decode('utf-8')
      timestamp = request.headers['x-webhook-timestamp']

      sign_str = timestamp + raw_body
      message = bytes(sign_str, 'utf-8')
      secret_key = bytes("<client-secret>", 'utf-8')
      signature = base64.b64encode(
          hmac.new(secret_key, message, digestmod=hashlib.sha256).digest()
      )
      return signature.decode("utf-8")  # compare with x-webhook-signature header value
  ```
</CodeGroup>

<Note>
  Cashfree signs webhooks using the client secret that was active at the time the event was sent. If you have rotated your client secret, keep the previous secret active until all in-flight webhooks from that period have been processed.
</Note>

For additional language samples, see [Secure ID webhook signature verification](/docs/api-reference/vrs/webhook-signature-verification) and [PPI webhook signature verification](/docs/api-reference/prepaid-payment-instruments/webhook-signature-verification).

### Payouts V1: Cashgram

Cashgram webhooks use form-encoded POST parameters rather than a JSON body. The signature is computed as follows:

1. Collect all POST parameters except `signature` into an array.
2. Sort the array by key in ascending alphabetical order.
3. Concatenate all non-empty values in the sorted order to form `postData`.
4. Compute `HMAC-SHA256(postData, clientSecret)` and Base64-encode the result.
5. Compare the result to the received `signature` parameter. Reject the request if the values don't match.

```php PHP theme={"dark"}
$data      = $_POST;
$signature = $_POST["signature"];
unset($data["signature"]);

ksort($data);

$postData = "";
foreach ($data as $key => $value) {
  if (strlen($value) > 0) {
    $postData .= $value;
  }
}

$computedSignature = base64_encode(
  hash_hmac("sha256", $postData, $clientSecret, true)
);

if ($signature === $computedSignature) {
  // Signature is valid. Process the event.
} else {
  // Signature mismatch. Reject the request.
}
```

For the full parameter list and ordering rules, see [Cashgram webhooks](/docs/payouts/cashgram/integration/webhooks).

***

## Sample payloads

The event discriminator field is `type` for Payment Gateway and Payouts V2, and `event_type` for Secure ID and PPI. Cashgram payloads are form-encoded, not JSON. Always validate field names and enum values against the product page for your API version.

<CodeGroup>
  ```json Payments: PAYMENT_SUCCESS theme={"dark"}
  {
    "data": {
      "order": {
        "order_id": "order_1234",
        "order_amount": 500.00,
        "order_currency": "INR",
        "order_tags": null
      },
      "payment": {
        "cf_payment_id": 9876543210,
        "payment_status": "SUCCESS",
        "payment_amount": 500.00,
        "payment_currency": "INR",
        "payment_message": "Transaction successful",
        "payment_time": "2024-01-15T10:30:00+05:30",
        "bank_reference": "TXN123456789",
        "auth_id": null,
        "payment_method": {
          "upi": {
            "channel": "collect",
            "upi_id": "user@upi"
          }
        }
      },
      "customer_details": {
        "customer_name": "John Doe",
        "customer_id": "CUST001",
        "customer_email": "john@example.com",
        "customer_phone": "9999999999"
      }
    },
    "event_time": "2024-01-15T10:30:05+05:30",
    "type": "PAYMENT_SUCCESS"
  }
  ```

  ```json Payouts V2: TRANSFER_SUCCESS theme={"dark"}
  {
    "data": {
      "transfer_id": "JUNOB2018",
      "cf_transfer_id": "123456",
      "status": "SUCCESS",
      "status_code": "SENT_TO_BENEFICIARY",
      "status_description": "The transfer has been initiated via the partner bank successfully. The request is waiting to be processed at the beneficiary bank to do the credit to the end beneficiary.",
      "beneficiary_details": {
        "beneficiary_id": "JOHN18011",
        "beneficiary_instrument_details": {
          "bank_account_number": "7766671501729",
          "bank_ifsc": "SBIN0000003"
        }
      },
      "transfer_amount": 1,
      "transfer_service_charge": 1,
      "transfer_service_tax": 0.18,
      "transfer_mode": "BANK",
      "transfer_utr": "TESTR92023012200543116",
      "fundsource_id": "CASHFREE_1",
      "added_on": "2021-11-24T13:39:25Z",
      "updated_on": "2021-11-24T13:40:27Z"
    },
    "event_time": "2024-07-25T17:43:37",
    "type": "TRANSFER_SUCCESS"
  }
  ```

  ```json Secure ID: VKYC_AUDITOR_REVIEW_COMPLETED theme={"dark"}
  {
    "event_type": "VKYC_AUDITOR_REVIEW_COMPLETED",
    "event_time": "2025-05-22T03:51:14Z",
    "version": "v1",
    "data": {
      "verification_id": "test333",
      "reference_id": 10449,
      "user_reference_id": 10259,
      "user_id": "test11",
      "status": "AUDITOR_REVIEWED",
      "sub_status": "AUDITOR_APPROVED",
      "vkyc_link": "https://forms.cashfree.net/verification/ashortCode",
      "link_expiry": "2025-06-21",
      "recording_link": "https://storage.cashfree.com/vkyc/recordings/test333.mp4",
      "meeting_schedule": null,
      "auditor_remarks": "Looks Good",
      "agent_remarks": "Good to go"
    }
  }
  ```

  ```json Secure ID: BANK_ACCOUNT_VERIFICATION_SUCCESS theme={"dark"}
  {
    "event_type": "BANK_ACCOUNT_VERIFICATION_SUCCESS",
    "event_time": "2023-07-19 10:46:16",
    "version": "v2",
    "data": {
      "reference_id": 1294785793,
      "user_id": "123123",
      "name_at_bank": "John Doe",
      "amount_deposited": "1.04",
      "bank_name": "YES BANK",
      "utr": "404223241811",
      "city": "MUMBAI",
      "branch": "SANTACRUZ, MUMBAI",
      "micr": 400532038,
      "name_match_score": "90.00",
      "name_match_result": "GOOD_PARTIAL_MATCH",
      "account_status": "VALID",
      "account_status_code": "ACCOUNT_IS_VALID"
    }
  }
  ```

  ```json PPI: PPI_CREDIT_SUCCESS theme={"dark"}
  {
    "event_type": "PPI_CREDIT_SUCCESS",
    "event_time": "2006-01-02T15:04:05Z",
    "data": {
      "credit_id": "CREDIT126345",
      "cf_credit_id": "8901234567890123456",
      "wallet_id": "WALLET936721",
      "user_id": "USER827364",
      "amount": 100.5,
      "sub_wallet": {
        "cf_sub_wallet_id": "35246543210987654321",
        "name": "Gift Wallet",
        "type": "GIFT_PPI",
        "status": "ACTIVE",
        "balance": 1500.75
      },
      "status": "SUCCESS",
      "remarks": "Refund for order 123",
      "initiated_at": "2025-09-02T10:15:30Z",
      "processed_at": "2025-09-02T10:20:45Z"
    }
  }
  ```

  ```json Cashgram: CASHGRAM_REDEEMED (form parameters) theme={"dark"}
  {
    "referenceId": "JUNOB2018",
    "cashgramCode": "CASHGRAM123",
    "transferId": "TRF12345",
    "amount": "500.00",
    "beneficiaryName": "John Doe",
    "beneficiaryPhone": "9999999999",
    "status": "REDEEMED",
    "statusMessage": "Transfer Successful",
    "utr": "TESTR12345",
    "addedOn": "2024-01-15 10:30:00",
    "signature": "<base64-encoded-hmac>"
  }
  ```
</CodeGroup>

<Note>
  The Cashgram payload shows the POST parameter names formatted as JSON for readability. In practice, Cashgram webhooks arrive as `application/x-www-form-urlencoded` POST parameters, not as a JSON body. See [Cashgram webhooks](/docs/payouts/cashgram/integration/webhooks) for the authoritative parameter list.
</Note>
