Subscription Webhooks
In this article, you will learn about the various webhooks involved in Cashfree Subscriptions.
Webhooks will be sent to your configured endpoint as a POST request with the body containing the various parameters specifying the details of each event. Each request contains a type parameter that identifies the webhook type.
Below are the various events that can be sent to your webhook endpoint.
- SUBSCRIPTION_STATUS_CHANGE
- SUBSCRIPTION_AUTH_STATUS
- SUBSCRIPTION_PAYMENT_NOTIFICATION_INITIATED
- SUBSCRIPTION_PAYMENT_SUCCESS
- SUBSCRIPTION_PAYMENT_CANCELLED
- SUBSCRIPTION_PAYMENT_DECLINED
- SUBSCRIPTION_REFUND_STATUS
- SUBSCRIPTION_CARD_EXPIRY_REMINDER
Click here to know how to configure webhooks.
SUBSCRIPTION_STATUS_CHANGE
Whenever a subscription is created it goes to initialised state and customer is expected to authorise it. The list of statuses where this webhook will get triggered are:
- ACTIVE
- ON HOLD
- COMPLETED
- CUSTOMER CANCELLED
- CUSTOMER PAUSED
- EXPIRED
- LINK EXPIRED
Sample Payload
{
"data": {
"subscription_details": {
"cf_subscription_id": "123456",
"subscription_id": "Demo_Subscription",
"subscription_status": "ACTIVE",
"subscription_expiry_time": "2024-01-14T23:00:08+05:30",
"subscription_first_charge_time": "2024-01-10T23:00:08+05:30"
},
"customer_details": {
"customer_name": "john",
"customer_email": "[email protected]",
"customer_phone": "9908730221"
},
"plan_details": {
"plan_id": "plan12345",
"plan_name": "Plan Name",
"plan_type": "ON_DEMAND",
"plan_max_cycles": 10,
"plan_recurring_amount": 100.00,
"plan_max_amount": 1000.00,
"plan_currency": "INR",
"plan_note": "Note",
"plan_status": "ACTIVE"
},
"authorization_details": {
"authorization_amount": 100,
"authorization_amount_refund": true,
"approve_by_time": "2022-02-09T18:04:34+05:30",
"authorization_reference": "6595231908096894505959",
"authorization_time": "2022-08-03T16:09:51",
"authorization_status": "SUCCESS",
"payment_id": "123",
"payment_method": "DEBIT_CARD",
"instrument_id": "hsdg9"
},
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_subscription_id": "Demo_Subscription",
"gateway_plan_id": "plan12345",
"gateway_auth_id": "6595231908096894505959"
}
},
"event_time": "2023-01-03T11:16:10+05:30",
"type": "SUBSCRIPTION_STATUS_CHANGE"
}
SUBSCRIPTION_AUTH_STATUS
The event is triggered when the checkout is completed by the customer for success and failed cases.
Sample Payload
{
"data": {
"payment_id": "12345",
"cf_payment_id": "67890",
"cf_txn_id": "789012",
"cf_order_id": 98765,
"subscription_id": "sub12345",
"cf_subscription_id": "sub67890",
"payment_type": "DEBIT_CARD",
"authorization_details": {
"authorization_amount": 100,
"authorization_amount_refund": true,
"approve_by_time": "2024-07-20T16:09:50+05:30",
"authorization_reference": "6595231908096894505959",
"authorization_time": "2024-07-20T16:09:51",
"authorization_status": "ACTIVE",
"payment_id": "123",
"payment_method": "DEBIT_CARD",
"instrument_id": "hsdg9"
},
"payment_amount": 200.75,
"payment_schedule_date": "2024-07-20",
"payment_initiated_date": "2024-07-20",
"payment_remarks": "auth payment",
"retry_attempts": 0,
"failureDetails": null,
"payment_status": "SUCCESS",
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_subscription_id": "Demo_Subscription",
"gateway_payment_id": "payment12345"
}
},
"event_time": "2024-07-20T11:16:10+05:30",
"type": "SUBSCRIPTION_AUTH_STATUS"
}
SUBSCRIPTION_PAYMENT_NOTIFICATION_INITIATED
This webhook is used to inform the merchant that we have sent a notification of payment to the customer.
Sample Payload
{
"data": {
"payment_id": "12345",
"cf_payment_id": "67890",
"cf_txn_id": "789012",
"cf_order_id": 98765,
"subscription_id": "sub12345",
"cf_subscription_id": "sub67890",
"payment_type": "DEBIT_CARD",
"authorization_details": {
"authorization_amount": 100,
"authorization_amount_refund": true,
"approve_by_time": "2024-07-20T16:09:50+05:30",
"authorization_reference": "6595231908096894505959",
"authorization_time": "2024-07-20T16:09:51",
"authorization_status": "PENDING",
"payment_id": "123",
"payment_method": "DEBIT_CARD",
"instrument_id": "hsdg9"
},
"payment_amount": 200,
"payment_schedule_date": "2024-07-20",
"payment_initiated_date": "2024-07-20",
"payment_remarks": "payment",
"retry_attempts": 0,
"failureDetails": null,
"payment_status": "INITIALIZED",
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_subscription_id": "Demo_Subscription",
"gateway_payment_id": "payment12345"
}
},
"event_time": "2024-07-20T11:16:10+05:30",
"type": "SUBSCRIPTION_PAYMENT_NOTIFICATION_INITIATED"
}
SUBSCRIPTION_PAYMENT_SUCCESS
Sample Payload
{
"data": {
"payment_id": "12345",
"cf_payment_id": "67890",
"cf_txn_id": "789012",
"cf_order_id": 98765,
"subscription_id": "sub12345",
"cf_subscription_id": "sub67890",
"payment_type": "DEBIT_CARD",
"authorization_details": {
"authorization_amount": 100,
"authorization_amount_refund": true,
"approve_by_time": "2024-07-20T16:09:50+05:30",
"authorization_reference": "6595231908096894505959",
"authorization_time": "2024-07-20T16:09:51",
"authorization_status": "ACTIVE",
"payment_id": "123",
"payment_method": "DEBIT_CARD",
"instrument_id": "hsdg9"
},
"payment_amount": 200,
"payment_schedule_date": "2024-07-20",
"payment_initiated_date": "2024-07-20",
"payment_remarks": "payment",
"retry_attempts": 0,
"failureDetails": null,
"payment_status": "SUCCESS",
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_subscription_id": "Demo_Subscription",
"gateway_payment_id": "payment12345"
}
},
"event_time": "2024-07-20T11:16:10+05:30",
"type": "SUBSCRIPTION_PAYMENT_SUCCESS"
}
SUBSCRIPTION_PAYMENT_CANCELLED
Sample Payload
{
"data": {
"payment_id": "12345",
"cf_payment_id": "67890",
"cf_txn_id": "789012",
"cf_order_id": 98765,
"subscription_id": "sub12345",
"cf_subscription_id": "sub67890",
"payment_type": "DEBIT_CARD",
"authorization_details": {
"authorization_amount": 100,
"authorization_amount_refund": true,
"approve_by_time": "2024-07-18T16:09:50+05:30",
"authorization_reference": "6595231908096894505959",
"authorization_time": "2024-07-18T16:09:51",
"authorization_status": "ACTIVE",
"payment_id": "123",
"payment_method": "DEBIT_CARD",
"instrument_id": "hsdg9"
},
"payment_amount": 200,
"payment_schedule_date": "2024-07-20",
"payment_initiated_date": "2024-07-20",
"payment_remarks": "payment",
"retry_attempts": 0,
"failureDetails": {
"failureReason": "Subscription is not active"
},
"payment_status": "CANCELLED",
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_subscription_id": "Demo_Subscription",
"gateway_payment_id": "payment12345"
}
},
"event_time": "2024-07-20T11:16:10+05:30",
"type": "SUBSCRIPTION_PAYMENT_CANCELLED"
}
SUBSCRIPTION_PAYMENT_FAILED
Sample Payload
{
"data": {
"payment_id": "12345",
"cf_payment_id": "67890",
"cf_txn_id": "789012",
"cf_order_id": 98765,
"subscription_id": "sub12345",
"cf_subscription_id": "sub67890",
"payment_type": "DEBIT_CARD",
"authorization_details": {
"authorization_amount": 100,
"authorization_amount_refund": true,
"approve_by_time": "2024-07-18T16:09:50+05:30",
"authorization_reference": "6595231908096894505959",
"authorization_time": "2024-07-18T16:09:51",
"authorization_status": "ACTIVE",
"payment_id": "123",
"payment_method": "DEBIT_CARD",
"instrument_id": "hsdg9"
},
"payment_amount": 200,
"payment_schedule_date": "2024-07-20",
"payment_initiated_date": "2024-07-20",
"payment_remarks": "payment",
"retry_attempts": 0,
"failureDetails": {
"failureReason": "Insufficient balance"
},
"payment_status": "FAILED",
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_subscription_id": "Demo_Subscription",
"gateway_payment_id": "payment12345"
}
},
"event_time": "2024-07-20T11:16:10+05:30",
"type": "SUBSCRIPTION_PAYMENT_FAILED"
}
SUBSCRIPTION_REFUND_STATUS
This event is triggered whenever a refund status of a transaction will be success, failed, or cancelled.
Sample Payload
{
"data": {
"payment_id": "pay8643",
"cf_payment_id": "863782648",
"refund_id": "refund2",
"cf_refund_id": "ref_212",
"refund_amount": 100,
"refund_note": "test",
"refund_speed": "INSTANT",
"refund_status": "SUCCESS",
"failure_details": null,
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_payment_id": "pay8643",
"gateway_refund_id": "refund2"
}
},
"event_time": "2023-01-03T11:16:10+05:30",
"type": "SUBSCRIPTION_REFUND_STATUS"
}
SUBSCRIPTION_CARD_EXPIRY_REMINDER
Sample Payload
{
"data": {
"subscription_details": {
"cf_subscription_id": "123456",
"subscription_id": "Demo_Subscription",
"subscription_status": "ACTIVE",
"subscription_expiry_time": "2024-05-14T23:00:08+05:30",
"subscription_first_charge_time": "2024-01-14T23:00:08+05:30"
},
"customer_details": {
"customer_name": "john",
"customer_email": "[email protected]",
"customer_phone": "9908730221"
},
"plan_details": {
"plan_id": "plan12345",
"plan_name": "Plan Name",
"plan_type": "ON_DEMAND",
"plan_max_cycles": 10,
"plan_recurring_amount": 100.00,
"plan_max_amount": 1000.00,
"plan_currency": "INR",
"plan_note": "Note",
"plan_status": "ACTIVE"
},
"authorization_details": {
"authorization_amount": 100,
"authorization_amount_refund": true,
"approve_by_time": "2024-01-14T18:04:34+05:30",
"authorization_reference": "6595231908096894505959",
"authorization_time": "2024-01-14T16:09:51",
"authorization_status": "SUCCESS",
"payment_id": "123",
"payment_method": "DEBIT_CARD",
"instrument_id": "hsdg9"
},
"payment_gateway_details": {
"gateway_name": "CASHFREE",
"gateway_subscription_id": "Demo_Subscription",
"gateway_plan_id": "plan12345",
"gateway_auth_id": "6595231908096894505959"
}
},
"cardExpiryDate": "2024-02-21",
"event_time": "2024-02-20T11:16:10+05:30",
"type": "SUBSCRIPTION_CARD_EXPIRY_REMINDER"
}
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
Actual signature is present in the header x-webhook-signature
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"
Updated 4 months ago