Dispute Webhooks
Learn in detail about dispute webhooks.
Dispute webhooks can be configured to receive automated notifications when disputes are created, updated and closed.
The webhook notification will be sent on all the URLs added and enabled under the dispute 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.
Webhook | Description |
---|---|
DISPUTE_CREATED | Dispute_created webhook will be triggered when a new dispute is created. |
DISPUTE_UPDATED | Dispute_updated webhook will be triggered when a dispute is updated, for example when a comment is added by the Cashfree team, when the dispute moves into further stages like Pre-arbitration, Arbitration, or when the status of the dispute changes. |
DISPUTE_CLOSED | This webhook will be triggered when a dispute is closed. |
Click here to know how to configure webhooks.
Dispute Created
Sample Payload
"{
"data": {
"dispute": {
"dispute_id": "433475258",
"dispute_type": "DISPUTE",
"reason_code": "1402",
"reason_description": "Duplicate Processing",
"dispute_amount": 3,
"created_at": "2023-06-15T21:49:48+05:30",
"updated_at": "2023-06-15T21:49:48+05:30",
"respond_by": "2023-06-18T23:59:59+05:30",
"dispute_status": ""DISPUTE_CREATED"",
"cf_dispute_remarks": "Dispute is created, please take action",
"dispute_action_on": "MERCHANT"
},
"order_details": {
"order_id": "order_1944392DR1kMTFYdIf8bI2awAcC3i9FTa",
"order_amount": 3,
"order_currency": "INR",
"cf_payment_id": 885473311,
"payment_amount": 3,
"payment_currency": "INR"
},
"customer_details": {
"customer_name": "Dileep Kumar s",
"customer_phone": "8000000000",
"customer_email": "[email protected]"
}
},
"event_time": ""2023-06-15T21:50:04+05:30",
"type": "DISPUTE_CREATED"
}"
Dispute Updated
Sample Payload
"{
"data": {
"dispute": {
"dispute_id": "433475257",
"dispute_type": "PRE_ARBITRATION",
"reason_code": "13.1",
"reason_description": "Merchandise / Services Not Received",
"dispute_amount": 40000,
"created_at": "2023-06-15T21:16:03+05:30",
"updated_at": "2023-06-15T21:19:15+05:30",
"respond_by": "2023-06-19T23:59:59+05:30",
"dispute_status": "PRE_ARBITRATION_CREATED",
"cf_dispute_remarks": "Pre Arbitration request has been raised for this case.\nTarget Date :: 2023-06-18T00:00 -> 2023-06-19T23:59:59.",
"dispute_update": "TYPE_UPDATE",
"dispute_action_on": "MERCHANT"
},
"order_details": {
"order_id": "order_1944392D4jHtCeVPPdTXkaUwg5cfnujQe",
"order_amount": 40000,
"order_currency": "INR",
"cf_payment_id": 885457437,
"payment_amount": 40000,
"payment_currency": "INR"
},
"customer_details": {
"customer_name": "Dileep Kumar s",
"customer_phone": "8000000000",
"customer_email": "[email protected]"
}
},
"event_time": "2023-06-15T21:20:24+05:30",
"type": "DISPUTE_UPDATED"
}"
Dispute Closed
Sample Payload
"{
"data": {
"dispute": {
"dispute_id": "433475257",
"dispute_type": "CHARGEBACK",
"reason_code": "4855",
"reason_description": "Goods or Services Not Provided",
"dispute_amount": 4500,
"created_at": "2023-06-15T21:16:03+05:30",
"updated_at": "2023-06-15T21:16:51+05:30",
"respond_by": "2023-06-18T00:00:00+05:30",
"resolved_at": "2023-06-15T21:16:51.682836678+05:30",
"dispute_status": "CHARGEBACK_MERCHANT_WON",
"cf_dispute_remarks": "Chargeback won by merchant"
},
"order_details": {
"order_id": "order_1944392D4jHtCeVPPdTXkaUwg5cfnujQe",
"order_amount": 4500,
"order_currency": "INR",
"cf_payment_id": 885457437,
"payment_amount": 4500,
"payment_currency": "INR"
},
"customer_details": {
"customer_name": "Dileep Kumar s",
"customer_phone": "8000000000",
"customer_email": "[email protected]"
}
},
"event_time": "2023-06-15T21:17:14+05:30",
"type": "DISPUTE_CLOSED"
}"
Payload Field Description
Field | Description | Example | Type |
---|---|---|---|
dispute_id | Cashfree’s unique ID to identify a dispute. | 433475257 | Long |
dispute_type | Type of dispute created. Possible values: - DISPUTE - RETRIEVAL - CHARGEBACK - PRE_ARBITRATION - ARBITRATION | Chargeback | String |
reason_code | Condition for which the customer is filing the case. Click here to know more about dispute reason codes. | 13.6 | String |
reason_description | Description of the reason code. Codes for Chargeback cases are specified by Card networks/NPCI. | Credit not processed | String |
dispute_amount | The amount for which the dispute has been created. | 4500 | BigDecimal |
created_at | Time on which the dispute was registered on Cashfree’s system. | 2023-06-15T21:16:03+05:30 | LocalDateTime |
updated_at | Time on which the dispute was updated. | 2023-06-15T21:16:51+05:30 | LocalDateTime |
respond_by | Time by which the merchant is expected to respond to the dispute. | 2023-06-18T00:00:00+05:30 | LocalDateTime |
dispute_status | Status of Dispute. All possible values are listed in the table below. | CHARGEBACK_CREATED | String |
cf_dispute_remarks | Any remarks specified by Cashfree on the dispute. | Please submit documents. | String |
dispute_update | Specifies what has been updated on the dispute. Possible Values: - STATUS_UPDATE - TYPE_UPDATE - COMMENT_UPDATE | TYPE_UPDATE | String |
dispute_action_on | Specifies whether the action is on Cashfree or Merchant at a time. Possible Values: - MERCHANT - CASHFREE | MERCHANT | String |
resolved_at | Time on which the dispute was resolved/closed. | 2023-06-15T21:16:51.682836678+05:30 | LocalDateTime |
event_time | Time at which dispute webhook was initiated. | 2023-06-15T21:16:51+05:30 | LocalDateTime |
type | Type of webhook. Possible Values: - DISPUTE_CREATED - DISPUTE_UPDATED - DISPUTE_CLOSED | DISPUTE_CREATED | String |
List of Dispute Status |
---|
DISPUTE_CREATED |
DISPUTE_DOCS_RECEIVED |
DISPUTE_UNDER_REVIEW |
DISPUTE_MERCHANT_WON |
DISPUTE_MERCHANT_LOST |
DISPUTE_MERCHANT_ACCEPTED |
DISPUTE_INSUFFICIENT_EVIDENCE |
RETRIEVAL_CREATED |
RETRIEVAL_DOCS_RECEIVED |
RETRIEVAL_UNDER_REVIEW |
RETRIEVAL_MERCHANT_WON |
RETRIEVAL_MERCHANT_LOST |
RETRIEVAL_MERCHANT_ACCEPTED |
RETRIEVAL_INSUFFICIENT_EVIDENCE |
CHARGEBACK_CREATED |
CHARGEBACK_DOCS_RECEIVED |
CHARGEBACK_UNDER_REVIEW |
CHARGEBACK_MERCHANT_WON |
CHARGEBACK_MERCHANT_LOST |
CHARGEBACK_MERCHANT_ACCEPTED |
CHARGEBACK_INSUFFICIENT_EVIDENCE |
PRE_ARBITRATION_CREATED |
PRE_ARBITRATION_DOCS_RECEIVED |
PRE_ARBITRATION_UNDER_REVIEW |
PRE_ARBITRATION_MERCHANT_WON |
PRE_ARBITRATION_MERCHANT_LOST |
PRE_ARBITRATION_MERCHANT_ACCEPTED |
PRE_ARBITRATION_INSUFFICIENT_EVIDENCE |
ARBITRATION_CREATED |
ARBITRATION_DOCS_RECEIVED |
ARBITRATION_UNDER_REVIEW |
ARBITRATION_MERCHANT_WON |
ARBITRATION_MERCHANT_LOST |
ARBITRATION_MERCHANT_ACCEPTED |
ARBITRATION_INSUFFICIENT_EVIDENCE |
Compute Signature and Verify
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));
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 4 months ago