Webhooks are delivered in addition to the polling APIs, not instead of them. Implement polling as a fallback to handle cases where webhook delivery is delayed or retried.
Signature verification
Every COU webhook request includes two headers for signature verification:| Header | Description |
|---|---|
X-Webhook-Signature | Hex-encoded HMAC-SHA256 signature of the payload. |
X-Webhook-Timestamp | Unix timestamp in milliseconds at which the webhook was sent. |
signedPayload = X-Webhook-Timestamp + "." + rawBody
expectedSignature = HexEncode(HMAC-SHA256(signedPayload, secretKey))
.) separator between the timestamp and the raw body. The signature is hex-encoded (lowercase), not Base64.
Always compute the signature from the raw request body string. Parsing and re-serialising JSON can change whitespace or field order, which causes verification to fail.
The following examples have not been tested in a production environment. Adapt them to your framework and language version before use.
const crypto = require("crypto");
function verifyWebhook(req) {
const rawBody = req.rawBody; // must be the raw string, not parsed JSON
const timestamp = req.headers["x-webhook-timestamp"];
const receivedSignature = req.headers["x-webhook-signature"];
const signedPayload = timestamp + "." + rawBody;
const expectedSignature = crypto
.createHmac("sha256", process.env.CASHFREE_SECRET_KEY)
.update(signedPayload)
.digest("hex");
const expected = Buffer.from(expectedSignature, "utf8");
const received = Buffer.from(receivedSignature || "", "utf8");
// timingSafeEqual throws if lengths differ, so check the length first
if (expected.length !== received.length || !crypto.timingSafeEqual(expected, received)) {
throw new Error("Signature mismatch. Reject this request.");
}
return JSON.parse(rawBody);
}
// Requires Java 17+ for HexFormat
import jakarta.servlet.http.HttpServletRequest;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.HexFormat;
public boolean verifyWebhook(HttpServletRequest request) throws Exception {
String rawBody = new String(request.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
String timestamp = request.getHeader("X-Webhook-Timestamp");
String receivedSignature = request.getHeader("X-Webhook-Signature");
String signedPayload = timestamp + "." + rawBody;
Mac mac = Mac.getInstance("HmacSHA256");
mac.init(new SecretKeySpec("<secret-key>".getBytes(StandardCharsets.UTF_8), "HmacSHA256"));
String expectedSignature = HexFormat.of()
.formatHex(mac.doFinal(signedPayload.getBytes(StandardCharsets.UTF_8)));
if (receivedSignature == null) {
return false;
}
return MessageDigest.isEqual(
expectedSignature.getBytes(StandardCharsets.UTF_8),
receivedSignature.getBytes(StandardCharsets.UTF_8));
}
# Assumes a Flask request object. For Django, replace request.data with request.body.
import hashlib
import hmac
def verify_webhook(request):
raw_body = request.data.decode("utf-8")
timestamp = request.headers["X-Webhook-Timestamp"]
received_signature = request.headers["X-Webhook-Signature"]
signed_payload = timestamp + "." + raw_body
expected_signature = hmac.new(
b"<secret-key>",
signed_payload.encode("utf-8"),
digestmod=hashlib.sha256,
).hexdigest()
return hmac.compare_digest(expected_signature, received_signature)
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/hex"
)
func verifyWebhook(timestamp, rawBody, receivedSig, secretKey string) bool {
signedPayload := timestamp + "." + rawBody
h := hmac.New(sha256.New, []byte(secretKey))
h.Write([]byte(signedPayload))
expected := hex.EncodeToString(h.Sum(nil))
return hmac.Equal([]byte(expected), []byte(receivedSig))
}
Common fields
Every COU webhook payload includes the following top-level fields regardless of event type:| Field | Type | Description |
|---|---|---|
webhook_id | string | Unique identifier for this webhook delivery. Use this to deduplicate retries. |
version | string | Webhook payload version. Current value is 1.0. |
event | string | Event type discriminator. See Event types. |
Event types
The following table lists the COU webhook events and what triggers each one:| Event | Trigger |
|---|---|
BILL_FETCH_STATUS | A bill fetch request has reached a terminal state (success or failure). |
BILL_VALIDATION_STATUS | A bill validation request has reached a terminal state (success or failure). |
BILL_PAYMENT_STATUS | A bill payment transaction has reached a terminal state (success or failure). |
BILLER_MDM_UPDATED | One or more biller master data management (MDM) records have been updated. |
BILL_FETCH_STATUS
Cashfree sends this event when aFETCH_AND_PAY flow bill fetch request completes.
Payload fields
ABILL_FETCH_STATUS payload contains the following fields:
| Field | Type | Description |
|---|---|---|
webhook_id | string | Unique webhook delivery identifier. |
version | string | Payload version. Current value is 1.0. |
event | string | Always BILL_FETCH_STATUS. |
status | string | Terminal status of the fetch operation. SUCCESS or FAILURE. |
agent_id | string | Unique ID of the Agent Institution that initiated the request. |
bill_fetch_ref_id | string | The ref_id returned by the Bill Fetch Request API. Use this to correlate the webhook with your original request. |
approval_ref_num | string | null | Internal reference number assigned by the biller on success. Present only on successful responses. |
response_code | string | null | Response code from the biller. 000 indicates success and any other value indicates a failure. |
response_reason | string | null | Human-readable description of the response code. |
compliance_resp_cd | string | null | Compliance failure code returned by the biller. Present only on failure. See Error Codes for every code and its retry category. |
compliance_reason | string | null | Human-readable failure reason. Present only on failure. See Error Codes. |
bill_details | object | null | Customer reference parameters echoed back from the request. Present only for FETCH_AND_PAY success. See bill_details object. |
biller_response | object | null | Bill details returned by the biller. Present only for FETCH_AND_PAY success. See biller_response object. |
additional_info | object | null | Biller-specific additional fields returned in the fetch response. Present only for FETCH_AND_PAY success. |
Sample payloads
{
"webhook_id": "BILL_FETCH_1398",
"version": "1.0",
"event": "BILL_FETCH_STATUS",
"status": "SUCCESS",
"agent_id": "CH01CH02INB516193127",
"bill_fetch_ref_id": "188417f3fd70452697bb632446862811942",
"approval_ref_num": "SWAR_REF_TEST_6702",
"response_code": "000",
"response_reason": "Successful",
"bill_details": {
"customer_params": {
"tag": [
{
"name": "Loan Account Number",
"value": "LN0098766702"
},
{
"name": "Mobile Number",
"value": "7428776702"
}
]
}
},
"biller_response": {
"customer_name": "Rahul Kumar",
"amount": "250.00",
"due_date": "2026-07-31",
"bill_date": "2026-07-20",
"bill_number": "SWAR_REF_TEST_6702",
"bill_period": "MONTHLY"
}
}
{
"webhook_id": "BILL_FETCH_1397",
"version": "1.0",
"event": "BILL_FETCH_STATUS",
"status": "FAILURE",
"agent_id": "CH01CH02INB516193127",
"bill_fetch_ref_id": "06b867dc716240b1b9bcbb243bb62811939",
"response_code": "200",
"response_reason": "Failure",
"compliance_resp_cd": "BFR004",
"compliance_reason": "Payment received for the billing period, no bill due"
}
BILL_VALIDATION_STATUS
Cashfree sends this event when aVALIDATE_AND_PAY flow bill validation request completes.
Payload fields
ABILL_VALIDATION_STATUS payload contains the following fields:
| Field | Type | Description |
|---|---|---|
webhook_id | string | Unique webhook delivery identifier. |
version | string | Payload version. Current value is 1.0. |
event | string | Always BILL_VALIDATION_STATUS. |
status | string | Terminal status of the validation. SUCCESS or FAILURE. |
agent_id | string | Unique ID of the Agent Institution that initiated the request. |
ref_id | string | The ref_id returned by the Bill Fetch Request API (validation flow). Use this to correlate the webhook with your original request. |
approval_ref_num | string | null | Internal reference number assigned by the biller on success. Present only on successful responses. |
response_code | string | null | Response code from the biller. 000 indicates success and any other value indicates a failure. |
response_reason | string | null | Human-readable description of the response code. |
compliance_resp_cd | string | null | Compliance failure code returned by the biller. Present only on failure. See Error Codes for every code and its retry category. |
compliance_reason | string | null | Human-readable failure reason. Present only on failure. See Error Codes. |
additional_info | object | null | Biller-specific additional fields returned in the validation response. |
Sample payloads
{
"webhook_id": "BILL_VALIDATION_2001",
"version": "1.0",
"event": "BILL_VALIDATION_STATUS",
"status": "SUCCESS",
"agent_id": "CH01CH02INB516193127",
"ref_id": "BV20240501GHI789",
"approval_ref_num": "AB234567",
"response_code": "000",
"response_reason": "Successful",
"additional_info": {
"tag": [
{ "name": "PlanName", "value": "Prepaid 499" }
]
}
}
{
"webhook_id": "BILL_VALIDATION_2002",
"version": "1.0",
"event": "BILL_VALIDATION_STATUS",
"status": "FAILURE",
"agent_id": "CH01CH02INB516193127",
"ref_id": "BV20240501JKL012",
"response_code": "200",
"response_reason": "Incorrect or invalid customer account",
"compliance_resp_cd": "BFR001",
"compliance_reason": "Incorrect or invalid customer account"
}
BILL_PAYMENT_STATUS
Cashfree sends this event when a bill payment transaction reaches a terminal state.Payload fields
ABILL_PAYMENT_STATUS payload contains the following fields:
| Field | Type | Description |
|---|---|---|
webhook_id | string | Unique webhook delivery identifier. |
version | string | Payload version. Current value is 1.0. |
event | string | Always BILL_PAYMENT_STATUS. |
status | string | Terminal status of the payment. SUCCESS or FAILURE. |
agent_id | string | Unique ID of the Agent Institution that initiated the payment. |
bill_fetch_ref_id | string | null | The ref_id from the preceding bill fetch or validation step. null for direct pay flow billers. |
transaction_ref_id | string | The transaction_ref_id returned by the Bill Payment Request API. Use this to correlate the webhook with your original payment request. |
approval_ref_num | string | null | Internal reference number assigned by the biller on success. Present only on successful responses. |
response_code | string | null | Response code from the biller. 000 indicates success and any other value indicates a failure. |
response_reason | string | null | Human-readable description of the response code. |
compliance_resp_cd | string | null | Compliance failure code returned by the biller. Present only on failure. See Error Codes for every code and its retry category. |
compliance_reason | string | null | Human-readable failure reason. Present only on failure. See Error Codes. |
bill_details | object | null | Customer reference parameters echoed back from the fetch step. See bill_details object. |
biller_response | object | null | Bill details from the biller at the time of payment. See biller_response object. |
additional_info | object | null | Biller-specific additional fields returned in the payment response. |
Sample payloads
{
"webhook_id": "BILL_PAYMENT_1320",
"version": "1.0",
"event": "BILL_PAYMENT_STATUS",
"status": "SUCCESS",
"agent_id": "CH01CH02INB516193127",
"bill_fetch_ref_id": "188417f3fd70452697bb632446862811942",
"transaction_ref_id": "CH016281FSNF02XE07D3",
"approval_ref_num": "DAD1BC4E15D14D96AA80204EE2FF25AB",
"response_code": "000",
"response_reason": "Successful",
"bill_details": {
"customer_params": {
"tag": [
{
"name": "Loan Account Number",
"value": "LN0098766702"
},
{
"name": "Mobile Number",
"value": "7428776702"
}
]
}
},
"biller_response": {
"customer_name": "Rahul Kumar",
"amount": "250.00",
"customer_convenience_fee": "0.00",
"due_date": "2026-07-31",
"bill_date": "2026-07-20",
"bill_number": "SWAR_REF_TEST_6702",
"bill_period": "MONTHLY"
}
}
{
"webhook_id": "BILL_PAYMENT_3002",
"version": "1.0",
"event": "BILL_PAYMENT_STATUS",
"status": "FAILURE",
"agent_id": "CH01CH02INB516193127",
"bill_fetch_ref_id": "188417f3fd70452697bb632446862811942",
"transaction_ref_id": "CH0162455XTQK9MIPNYZ",
"response_code": "200",
"response_reason": "Invalid combination of customer parameters",
"compliance_resp_cd": "BPR002",
"compliance_reason": "Invalid combination of customer parameters"
}
BILLER_MDM_UPDATED
Cashfree sends this event when one or more biller MDM records are updated. Use this event to refresh your local biller cache rather than polling the Billers Info API.Payload fields
ABILLER_MDM_UPDATED payload contains the following fields:
| Field | Type | Description |
|---|---|---|
webhook_id | string | Unique webhook delivery identifier. |
version | string | Payload version. Current value is 1.0. |
event | string | Always BILLER_MDM_UPDATED. |
billers | array | List of updated biller objects. See billers[] object. |
billers[] object
Each entry inbillers represents one updated biller and contains the following fields:
| Field | Type | Description |
|---|---|---|
biller_id | string | Unique BBPS biller identifier. |
biller_alias_name | string | Short alias name for the biller. |
biller_name | string | Full display name of the biller. |
biller_category_name | string | Biller category, for example Mobile Postpaid or Electricity. |
biller_mode | string | Mode of biller operation, for example ONLINE. |
biller_accepts_adhoc | boolean | Whether the biller accepts ad hoc (non-standard) payment amounts. |
biller_coverage | string | Geographic coverage of the biller, for example IND. |
fetch_requirement | string | Whether a bill fetch is required before payment. One of MANDATORY, OPTIONAL, NOT_SUPPORTED. |
payment_amount_exactness | string | Payment amount constraint, for example EXACT, EXACT_UP, or ANY. |
biller_response_type | string | Type of response returned by the biller, for example RICH or SIMPLE. |
selection_type | string | Whether the biller supports single or multiple account selection. |
support_bill_validation | string | Whether the biller supports bill validation flow. Y or N. |
biller_effctv_from | string | Date from which this biller configuration is effective (YYYY-MM-DD). |
biller_effctv_to | string | Date until which this biller configuration is effective (YYYY-MM-DD). |
biller_description | string | Human-readable description of the biller. |
support_pending_status | string | Whether the biller supports pending payment status. Y or N. |
support_deemed | string | Whether deemed success is supported for this biller. Y or N. |
biller_time_out | string | Timeout in seconds for biller requests. |
biller_ownership | string | Ownership type of the biller. |
plan_mdm_requirement | string | Whether plan MDM is required. One of MANDATORY, OPTIONAL, NOT_SUPPORTED. |
status | string | Current status of the biller. ACTIVE or INACTIVE. |
biller_customer_params | array | Customer input parameters required to identify the account. Each entry has param_name, data_type, optional, min_length, max_length, regex, visibility. |
biller_payment_modes | array | Supported payment modes. Each entry has payment_mode, min_limit, max_limit (in rupees), support_pending_status. |
biller_payment_channels | array | Supported payment channels. Each entry has payment_channel, min_limit, max_limit (in rupees), support_pending_status. |
biller_response_params | object | null | Amount options supported by the biller. Contains amount_options[], each with an amount_breakup_set list. |
biller_additional_info | array | Additional information parameters returned by the biller in the fetch or validation response. |
biller_additional_info_payment | array | Additional information parameters returned by the biller in the payment response. |
plan_additional_info | array | Additional information parameters for plan MDM. |
interchange_fee_conf | array | Interchange fee configuration entries. Each entry has mti, response_code, fees, default_fee, effctv_from. |
interchange_fee | array | Interchange fee definitions. Each entry has fee_code, fee_desc, fee_direction, and interchange_fee_details[] with tran_amt_range_min, tran_amt_range_max, percent_fee, flat_fee, effctv_from, effctv_to. |
Sample payload
{
"webhook_id": "MDM_UPDATE_4001",
"version": "1.0",
"event": "BILLER_MDM_UPDATED",
"billers": [
{
"biller_id": "VODA00000MUM03",
"biller_alias_name": "Vodafone MUM",
"biller_name": "Vodafone Mumbai",
"biller_category_name": "Mobile Postpaid",
"biller_mode": "ONLINE",
"biller_accepts_adhoc": false,
"biller_coverage": "IND",
"fetch_requirement": "MANDATORY",
"payment_amount_exactness": "EXACT",
"biller_response_type": "RICH",
"selection_type": "SINGLE",
"support_bill_validation": "N",
"biller_effctv_from": "2020-01-01",
"biller_effctv_to": "9999-12-31",
"biller_description": "Vodafone postpaid bills for Mumbai circle",
"support_pending_status": "N",
"support_deemed": "N",
"biller_time_out": "60",
"biller_ownership": "B",
"plan_mdm_requirement": "NOT_SUPPORTED",
"status": "ACTIVE",
"biller_customer_params": [
{
"param_name": "Mobile Number",
"data_type": "NUMERIC",
"optional": false,
"min_length": 10,
"max_length": 10,
"regex": "^[6-9][0-9]{9}$",
"visibility": true
}
],
"biller_payment_modes": [
{
"payment_mode": "Internet Banking",
"min_limit": 1,
"max_limit": 100000,
"support_pending_status": "N"
},
{
"payment_mode": "UPI",
"min_limit": 1,
"max_limit": 100000,
"support_pending_status": "N"
}
],
"biller_payment_channels": [
{
"payment_channel": "INT",
"min_limit": 1,
"max_limit": 100000,
"support_pending_status": "N"
}
],
"biller_response_params": {
"amount_options": [
{
"amount_breakup_set": ["BASE_BILL_AMOUNT"]
}
]
},
"biller_additional_info": [],
"biller_additional_info_payment": [],
"plan_additional_info": [],
"interchange_fee_conf": [],
"interchange_fee": []
}
]
}
Nested object schemas
bill_details object
Customer reference parameters echoed back from the bill fetch request.| Field | Type | Description |
|---|---|---|
customer_params | object | Wrapper for customer input fields. |
customer_params.tag | array | List of customer input parameter entries. Each entry has the following fields: |
customer_params.tag[].name | string | Parameter name as defined in biller MDM, for example Mobile Number. |
customer_params.tag[].value | string | Value provided by the customer. |
customer_params.tag[].min_amount | string | Minimum payable amount for this input combination, in rupees. Present only when applicable. |
customer_params.tag[].max_amount | string | Maximum payable amount for this input combination, in rupees. Present only when applicable. |
customer_params.tag[].amount_multiple | string | Amount must be a multiple of this value, in rupees. Present only when applicable. |
biller_response object
Bill details returned by the biller. Present on successfulFETCH_AND_PAY bill fetch and bill payment events.
| Field | Type | Description |
|---|---|---|
customer_name | string | Customer name as returned by the biller. |
amount | string | Bill amount in rupees. |
due_date | string | Bill due date in YYYY-MM-DD format. |
bill_date | string | Bill generation date in YYYY-MM-DD format. |
bill_number | string | Bill number assigned by the biller. |
bill_period | string | Billing period, for example MONTHLY or AUG2024. |
customer_convenience_fee | string | Convenience fee applicable to the customer in rupees. |
tag | array | Additional biller-defined key-value pairs. Each entry has a name and a value. Present only when the biller returns additional tag data. |
biller_response object, fields that the biller does not return are omitted.
Handling retries
Cashfree retries webhook delivery if your endpoint does not respond with HTTP2xx within the timeout window. To handle retries safely:
- Respond immediately: Return HTTP
200as soon as you receive the request, before performing any downstream processing. - Deduplicate by
webhook_id: Store processedwebhook_idvalues and skip processing if you have already handled a given ID. - Verify the signature: Reject any request where signature verification fails.