Auto-Refund Webhooks

Auto-refund webhooks can be configured to receive automated notifications when auto-refunds are initiated, processed and delayed. Auto-refunds are refunds triggered automatically for unsuccessful payments, disputes, duplicate payments; or instances where payments are received directly to your VPA, regardless of whether an order has been generated.

Webhook notifications are sent to every URL added and enabled under Auto-refund Webhooks. Merchants can add new URLs and enable or disable existing ones for auto-refund webhooks at any point in time. The changes are reflected instantaneously.

You can learn more on configuring webhooks.


Sample Payload

{
  "data": {
    "auto_refund": {
      "event": "AUTO-REFUND",
      "cf_refund_id": 1243460973,
      "cf_payment_id": "2148333968",
      "bank_reference": "234928698581",
      "order_id": "order_1944392Tpba8y2fHcHVx0SwREojp51Jgr",
      "refund_amount": 39,
      "refund_currency": "INR",
      "refund_type": "PAYMENT_AUTO_REFUND",
      "refund_arn": "205907014017",
      "refund_status": "SUCCESS",
      "status_description": "Auto-Refund processed successfully",
      "refund_reason": "Multiple payments were performed against same order.",
      "created_at": "2023-08-11T14:08:28+05:30",
      "processed_at": null,
      "refund_charge": 0,
      "refund_splits": null,
      "metadata": null
    }
  },
  "event_time": "2023-08-11T14:10:21+05:30",
  "type": "AUTO_REFUND_STATUS_WEBHOOK"
}

Find the description for each field of the payload below:

FieldDescriptionExample
cf_refund_idIt represents the ID created by Cashfree Payments to identify the refund.1243460973
cf_payment_idIt represents the ID created by Cashfree Payments to identify the payment for the initiated refund.2148333968
order_idIt represents the ID created by you to identify the order for which the refund is initiated.sampleorder0413
bank_referenceIt represents the bank reference number for the transaction.234928698581
refund_currencyIt represents the currency of the refund amount.INR
refund_amountIt represents the refunded amount.2
refund_typeIt represents the refund type. The value will always be PAYMENT_AUTO_REFUND.PAYMENT_AUTO_REFUND
refund_arnIt represents the bank reference number for the refund.205907014017
refund_statusIt represents the status of the refund (either SUCCESS or INITIATED)SUCCESS
status_descriptionIt represents the description of the refund status
(Two possible values:
Auto-Refund initiated successfully or
Auto-Refund processed successfully).
Auto-Refund initiated successfully
created_atIt represents the time of auto-refund creation.2022-02-28T12:54:25+05:30
processed_atIt represents when the auto-refund was processed successfully.2022-02-28T13:04:27+05:30
refund_reasonIt represents the reason of initiation of Auto-refund
(Possible values:
Credit was received for an incomplete payment.
Refunded to customer for Dispute.
Payment made directly to your VPA without order creation.
Multiple payments were performed against same order.
Payment details (amount, bank, etc.) do not match with created order or the payment was made after TTL.)
Multiple payments were performed against same order
refund_chargeIt represents the charges for processing refund in INR.0
refund_splitsIt represents the refund split details.[]
metadataIt represents the additional metadata.null
event_timeIt represents when the refund webhook was initiated.2022-02-28T13:04:28+05:30
typeIt represents the webhook type. The value will always be AUTO_REFUND_STATUS_WEBHOOK.AUTO_REFUND_STATUS_WEBHOOK

Compute and Verify Signature

Sample Code

Verify Signature using SDK

var express = require('express')
import { Cashfree } from "cashfree-pg"; 
var app = express()

Cashfree.XClientId = "<x-client-id>";
Cashfree.XClientSecret = "<x-client-secret>";
Cashfree.XEnvironment = Cashfree.Environment.SANDBOX;

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
                }
            }
        }
    }

Compute Signature and Verify manually

function verify(ts, rawBody){
    const body = req.headers["x-webhook-timestamp"] + req.rawBody;
    const secretKey = "<your secret key>";
    let genSignature = crypto.createHmac('sha256',secretKey).update(body).digest("base64");
    return genSignature
}
func VerifySignature(expectedSig string, ts string, body string) (string, error) {
	t := time.Now()
	currentTS := t.Unix()
	if currentTS-ts > 1000*300 {
		return "", errors.New("webhook delivered too late")
	}
	signStr := strconv.FormatInt(ts, 10) + body
	fmt.Println("signing String: ", signStr)
	key := ""
	h := hmac.New(sha256.New, []byte(key))
	h.Write([]byte(signStr))
	b := h.Sum(nil)
	return base64.StdEncoding.EncodeToString(b), nil
}

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');
		$ts = getallheaders()['x-webhook-timestamp'];
  
    $signStr = $ts . $rawBody;
    $key = "";
    $computeSig = base64_encode(hash_hmac('sha256', $signStr, $key, true));
    return $computeSig;
}
public String generateSignature() {

	bufferedReader = request.getReader();
  String line;
  while ((line = bufferedReader.readLine()) != null) {
     stringBuilder.append(line).append('\n');
  }      
  String payload = stringBuilder.toString();
  String timestamp = request.getHeader("x-webhook-timestamp");
      
  String data = timestamp+payload;

  String secretKey = "SECRET-KEY"; // Get secret key from Cashfree Merchant Dashboard;
  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()));
  return computed_signature; // compare with "x-webhook-signature"
}
import base64
import hashlib
import hmac

def generateSignature():    
    # Get the raw body from the request
    raw_body = request.data
  
    # Decode the raw body bytes into a string
    payload = raw_body.decode('utf-8')

    #verify_signature
    timestamp = request.headers['x-webhook-timestamp']
    
    signatureData = timestamp+payload
    message = bytes(signatureData, 'utf-8')
    secretkey=bytes("Secret_Key",'utf-8') #Get Secret_Key from Cashfree Merchant Dashboard.
    signature = base64.b64encode(hmac.new(secretkey, message, digestmod=hashlib.sha256).digest())
    computed_signature = str(signature, encoding='utf8')
    return computed_signature #compare with "x-webhook-signature"