Instrument Webhook
Instrument webhooks can be configured by merchants to receive asynchronous notifications for every update on the instrument status. Merchants must use the instrument status updates to change the status on their servers because of the actions taken by entities apart from the merchant. For example, if the card instrument is deactivated by the issuing bank, then notification would be sent to Cashfree Payments by the issuing bank via the respective card network. Cashfree Payments would update the instrument status to deactivated but the merchant would not have visibility of the instrument deactivation. In this case, merchants can subscribe to the instrument webhooks to stay updated with the recent instrument status update.
The Instrument webhooks can be configured in such a way that each instrument status update can be consumed on a different URL. For example, for Instrument Active status update and Instrument Deactivated status update merchants can configure different URLs and consume the webhook response. Currently, we support webhook only for instrument active notifications.
The webhook notification will be sent on all the URLs added and enabled under instrument webhook. Merchants can add new URLs and enable or disable existing URLs for instrument webhook at any point in time, and it will reflect instantly.
Click here to know how to configure webhooks.
Sample Payload
{
"data":{
"instrument":{
"customer_id":"siddhesh199721",
"afa_reference":"887316963",
"instrument_id":"af250dc5-e5e5-4e7d-a7cf-3f446741fa54",
"instrument_type":"card",
"instrument_uid":"680cd7171583f9f64b426983d4501d6941b462932ce5f626be78392d5ec42660",
"instrument_display":"XXXXXXXXXXXX6854",
"instrument_status":"ACTIVE",
"added_at":"2022-04-14T10:42:59+05:30",
"instrument_meta":{
"card_network":"visa",
"card_bank_name":"HDFC BANK",
"card_country":"IN",
"card_type":"credit",
"card_token_details":null
}
}
},
"event_time":"2022-04-14T10:44:14+05:30",
"type":"INSTRUMENT_ACTIVE_WEBHOOK"
}
Payload Field Description
Field | Description | Example |
---|---|---|
customer_id (String) | The customer ID associated with the save instrument request. It is sent by the merchant as a part of create order request in the customer id field. | user199721 |
afa_reference (String) | cf_payment_id of the successful transaction done while saving instrument. | 887316963 |
instrument_id (String) | The reference id of the saved instrument. Unique for a save instrument request but not unique for a particular instrument like card number or VPA. | af250dc5-e5e5-4e7d-a7cf-3f446741fa54 |
instrument_type (String) | Type of instrument - card, vpa | card |
instrument_uid (String) | Unique identifier for the instrument. For example, if a same card is saved by 2 different customers the instrument_uid would be same for both the cards. | 680cd7171583f9f64b426983d4501d6941b462932ce5f626be78392d5ec42660 |
instrument_display (String) | The display text for the saved instrument. For example, in case of saved cards the first 12 digits would be masked and only the last 4 digits would be visible in the instrument display string. It can be displayed to the customer for selecting a saved instrument for payment. | XXXXXXXXXXXX6854 |
instrument_status (String) | The status of saved instrument - INACTIVE, ACTIVE | ACTIVE |
added_at (String) | Timestamp at which instrument was saved. | 2022-04-14T10:42:59+05:30 |
card_network (String) | The card network to which the saved card belongs. Visa, Mastercard, Rupay, etc. | visa |
card_bank_name (String) | Card issuing bank name. | HDFC BANK |
card_country (String) | Card issuing country code. | IN |
card_type (String) | debit, credit, prepaid, etc. | debit |
card_token_details (Object) | Additional card token details like card token expiry, card token would be passed in this object. Note: Currently, the field is always passed as null. | null |
event_time (String) | Time at which webhook was initiated. | 2022-02-28T13:04:28+05:30 |
type (String) | Type of the instrument webhook. | INSTRUMENT_ACTIVE_WEBHOOK |
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 5 months ago