Refund Webhooks

Refund webhooks can be configured to receive automated notifications for refunds when either they are successfully processed or are cancelled. Merchants can build their own downstream systems like auto-updating their refund status, and sending a communication to customers by consuming refund webhooks.

The webhook notification will be sent on all the URLs added and enabled under the refund webhook. Merchants can add new URLs and enable or disable existing URLs for refund webhook at any point in time and it will be reflected instantaneously.

Click here to know how to configure webhooks.

Sample Payload


{
   "data":{
"refund":{
         "cf_refund_id":11325632,
         "cf_payment_id":789727431,
         "refund_id":"refund_sampleorder0413",
         "order_id":"sampleorder0413",
         "refund_amount":2.00,
         "refund_currency":"INR",
         "entity":"Refund",
         "refund_type":"MERCHANT_INITIATED",
         "refund_arn":"205907014017",
         "refund_status":"SUCCESS",
         "status_description":"Refund processed successfully",
         "created_at":"2022-02-28T12:54:25+05:30",
         "processed_at":"2022-02-28T13:04:27+05:30",
         "refund_charge":0,
         "refund_note":"Test",
         "refund_splits":[
            {
               "merchantVendorId":"sampleID12345",
               "amount":1,
               "percentage":null
            },
            {
               "merchantVendorId":"otherVendor",
               "amount":1,
               "percentage":null
            }
         ],
         "metadata":null,
         "refund_mode":"STANDARD"
      }
   },
   "event_time":"2022-02-28T13:04:28+05:30",
   "type":"REFUND_STATUS_WEBHOOK"
}

Payload Field Description

FieldDescriptionExample
cf_refund_idCashfree Payments ID for a refund.11325632
cf_payment_idCashfree Payments ID of the payment for which refund is initiated.789727431
refund_idMerchant’s refund ID of the refundrefund_sampleorder0413
order_idMerchant’s order Id of the order for which refund is initiated.sampleorder0413
refund_amountAmount that was refunded2.00
refund_currencyCurrency of the refund amount.INR
entityType of object (refund always)Refund
refund_typeType of refund (for webhook it will always be MERCHANT_INITIATED)MERCHANT_INITIATED
refund_arnThe bank reference number for refund205907014017
refund_statusStatus of the refund (either SUCCESS or CANCELLED in refund webhook)SUCCESS
status_descriptionDescription of refund statusRefund processed successfully
created_atTime of refund creation2022-02-28T12:54:25+05:30
processed_atTime when refund was processed successfully2022-02-28T13:04:27+05:30
refund_chargeCharges in INR for processing refund0
refund_noteNote added by merchant for the refundCancelled Order
refund_splitsRefund split details[]
metadataAdditional refund metadatanull
refund_modeMethod or speed of processing refund (INSTANT or STANDARD)STANDARD
event_timeTime at which refund webhook was initiated.2022-02-28T13:04:28+05:30
typeType of webhook. REFUND_STATUS_WEBHOOK alwaysREFUND_STATUS_WEBHOOK

Signature Verification

The signature must be used to verify if the request has not been tampered with. To verify the signature at your end, you will need your Cashfree PG secret key along with the payload.

timestamp is present in the header x-webhook-timestamp

timestamp := 1617695238078;  
signedPayload := $timestamp.$payload;
expectedSignature := Base64Encode(HMACSHA256($signedPayload, $merchantSecretKey));

Compute Signature and Verify

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"