> ## Documentation Index
> Fetch the complete documentation index at: https://www.cashfree.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# COU Webhooks

> Cashfree BBPS COU webhooks notify your server when a bill fetch, bill validation, bill payment, or biller MDM update reaches a terminal state.

Customer Operating Unit (COU) webhooks deliver real-time event notifications to your server when an asynchronous operation reaches a terminal state. Instead of continuously polling the response endpoints, Cashfree pushes updates to your registered endpoint as soon as a terminal state is reached.

<Note>
  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.
</Note>

## 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. |

**Algorithm:**

```text theme={"dark"}
signedPayload     = X-Webhook-Timestamp + "." + rawBody
expectedSignature = HexEncode(HMAC-SHA256(signedPayload, secretKey))
```

Note the **dot (`.`)** separator between the timestamp and the raw body. The signature is hex-encoded (lowercase), not Base64.

<Warning>
  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.
</Warning>

<Warning>
  The following examples have not been tested in a production environment. Adapt them to your framework and language version before use.
</Warning>

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  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);
  }
  ```

  ```java Java theme={"dark"}
  // 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));
  }
  ```

  ```python Python theme={"dark"}
  # 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)
  ```

  ```go Go theme={"dark"}
  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))
  }
  ```
</CodeGroup>

***

## 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). |

***

## Event types

The following table lists the COU webhook events and what triggers each one:

| Event | Trigger |
| - | - |
| [`BILL_FETCH_STATUS`](#bill_fetch_status) | A bill fetch request has reached a terminal state (success or failure). |
| [`BILL_VALIDATION_STATUS`](#bill_validation_status) | A bill validation request has reached a terminal state (success or failure). |
| [`BILL_PAYMENT_STATUS`](#bill_payment_status) | A bill payment transaction has reached a terminal state (success or failure). |
| [`BILLER_MDM_UPDATED`](#biller_mdm_updated) | One or more biller master data management (MDM) records have been updated. |

***

## BILL\_FETCH\_STATUS

Cashfree sends this event when a `FETCH_AND_PAY` flow bill fetch request completes.

### Payload fields

A `BILL_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](/docs/api-reference/other-apis/bbps-cou/error-codes) for every code and its retry category. |
| `compliance_reason` | string \| null | Human-readable failure reason. Present only on failure. See [Error Codes](/docs/api-reference/other-apis/bbps-cou/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](#bill_details-object). |
| `biller_response` | object \| null | Bill details returned by the biller. Present only for `FETCH_AND_PAY` success. See [biller\_response object](#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

<CodeGroup>
  ```json Success theme={"dark"}
  {
    "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"
    }
  }
  ```

  ```json Failure theme={"dark"}
  {
    "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"
  }
  ```
</CodeGroup>

***

## BILL\_VALIDATION\_STATUS

Cashfree sends this event when a `VALIDATE_AND_PAY` flow bill validation request completes.

### Payload fields

A `BILL_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](/docs/api-reference/other-apis/bbps-cou/error-codes) for every code and its retry category. |
| `compliance_reason` | string \| null | Human-readable failure reason. Present only on failure. See [Error Codes](/docs/api-reference/other-apis/bbps-cou/error-codes). |
| `additional_info` | object \| null | Biller-specific additional fields returned in the validation response. |

### Sample payloads

<CodeGroup>
  ```json Success theme={"dark"}
  {
    "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" }
      ]
    }
  }
  ```

  ```json Failure theme={"dark"}
  {
    "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"
  }
  ```
</CodeGroup>

***

## BILL\_PAYMENT\_STATUS

Cashfree sends this event when a bill payment transaction reaches a terminal state.

### Payload fields

A `BILL_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](/docs/api-reference/other-apis/bbps-cou/error-codes) for every code and its retry category. |
| `compliance_reason` | string \| null | Human-readable failure reason. Present only on failure. See [Error Codes](/docs/api-reference/other-apis/bbps-cou/error-codes). |
| `bill_details` | object \| null | Customer reference parameters echoed back from the fetch step. See [bill\_details object](#bill_details-object). |
| `biller_response` | object \| null | Bill details from the biller at the time of payment. See [biller\_response object](#biller_response-object). |
| `additional_info` | object \| null | Biller-specific additional fields returned in the payment response. |

### Sample payloads

<CodeGroup>
  ```json Success theme={"dark"}
  {
    "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"
    }
  }
  ```

  ```json Failure theme={"dark"}
  {
    "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"
  }
  ```
</CodeGroup>

***

## 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

A `BILLER_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). |

### billers\[] object

Each entry in `billers` 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

```json theme={"dark"}
{
  "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 successful `FETCH_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. |

In the `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 HTTP `2xx` within the timeout window. To handle retries safely:

1. **Respond immediately**: Return HTTP `200` as soon as you receive the request, before performing any downstream processing.
2. **Deduplicate by `webhook_id`**: Store processed `webhook_id` values and skip processing if you have already handled a given ID.
3. **Verify the signature**: Reject any request where signature verification fails.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.