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
Field | Description | Example |
---|---|---|
cf_refund_id | Cashfree Payments ID for a refund. | 11325632 |
cf_payment_id | Cashfree Payments ID of the payment for which refund is initiated. | 789727431 |
refund_id | Merchant’s refund ID of the refund | refund_sampleorder0413 |
order_id | Merchant’s order Id of the order for which refund is initiated. | sampleorder0413 |
refund_amount | Amount that was refunded | 2.00 |
refund_currency | Currency of the refund amount. | INR |
entity | Type of object (refund always) | Refund |
refund_type | Type of refund (for webhook it will always be MERCHANT_INITIATED) | MERCHANT_INITIATED |
refund_arn | The bank reference number for refund | 205907014017 |
refund_status | Status of the refund (either SUCCESS or CANCELLED in refund webhook) | SUCCESS |
status_description | Description of refund status | Refund processed successfully |
created_at | Time of refund creation | 2022-02-28T12:54:25+05:30 |
processed_at | Time when refund was processed successfully | 2022-02-28T13:04:27+05:30 |
refund_charge | Charges in INR for processing refund | 0 |
refund_note | Note added by merchant for the refund | Cancelled Order |
refund_splits | Refund split details | [] |
metadata | Additional refund metadata | null |
refund_mode | Method or speed of processing refund (INSTANT or STANDARD) | STANDARD |
event_time | Time at which refund webhook was initiated. | 2022-02-28T13:04:28+05:30 |
type | Type of webhook. REFUND_STATUS_WEBHOOK always | REFUND_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"
Subscribe to Developer Updates
Updated 6 months ago