Agent skill
Cashfree Webhooks Skill
Receive and verify Cashfree Payments webhooks (Payment Gateway, Payouts V2, Secure ID, PPI). Use when setting up a Cashfree webhook endpoint, debugging x-webhook-signature / x-webhook-timestamp verification, or handling events like PAYMENT_SUCCESS_WEBHOOK, PAYMENT_FAILED_WEBHOOK, REFUND_STATUS_WEBHOOK, SETTLEMENT_STATUS_WEBHOOK, TRANSFER_SUCCESS, BANK_ACCOUNT_VERIFICATION_SUCCESS and PPI_CREDIT_SUCCESS.
Install this skill
npx skills add hookdeck/webhook-skills --skill cashfree-webhooks
Cashfree Payments is an Indian payments company. One signing scheme covers four of its webhook products: Payment Gateway (payments, refunds, settlements, subscriptions, links, token vault, disputes), Payouts V2, Secure ID (Video KYC, bank account verification, reverse penny drop) and PPI wallets. Each delivery carries x-webhook-signature and x-webhook-timestamp headers.
Hookdeck setup note (source type Cashfree): Paste your Cashfree client secret (the Partner API key for partner webhooks). Legacy Payouts V1 and Cashgram webhooks use another scheme and fail verification.
When to Use This Skill
- How do I receive Cashfree webhooks?
- How do I verify a Cashfree webhook signature (
x-webhook-signature)? - What is the signed content: timestamp and body, with or without a separator?
- How do I handle
PAYMENT_SUCCESS_WEBHOOK,REFUND_STATUS_WEBHOOKorTRANSFER_SUCCESS? - Why does Cashfree keep retrying my webhook? (Anything other than HTTP 200 is a failure.)
- Why does my Cashfree webhook signature verification fail?
Verification (core)
| Item | Value |
|---|---|
| Signature header | x-webhook-signature: bare standard base64 (padded, 44 chars), no prefix |
| Timestamp header | x-webhook-timestamp |
| Algorithm | HMAC-SHA256 |
| Signed content | the x-webhook-timestamp value, then the raw body, no separator |
| Key | Cashfree client secret as UTF-8 (Partner API key for partner webhooks), not decoded |
const crypto = require('crypto');
// rawBody: Buffer of the exact request bytes (express.raw({ type: '*/*' }))
function verifyCashfree(rawBody, headers, secret) {
const sig = headers['x-webhook-signature'];
const ts = headers['x-webhook-timestamp'];
if (!sig || !ts || !/^[A-Za-z0-9+/]{43}=$/.test(sig)) return false; // validate before comparing
const expected = crypto.createHmac('sha256', Buffer.from(secret, 'utf8'))
.update(Buffer.concat([Buffer.from(ts, 'latin1'), rawBody])) // timestamp FIRST, body second
.digest('base64');
const a = Buffer.from(expected, 'latin1');
const b = Buffer.from(sig, 'latin1');
return a.length === b.length && crypto.timingSafeEqual(a, b); // whole header, case-sensitive
}
Python: hmac.new(secret.encode(), timestamp.encode("latin-1") + raw_, base64-encode it, then hmac.compare_digest on bytes against the header. Respond 401 for a missing, malformed or mismatching signature.
Cashfree's SDK has PGVerifyWebhookSignature (cashfree-pg), but the examples verify by hand so every framework treats the body as raw bytes. Never JSON.parse / JSON.stringify before verifying. Details: references/verification.md.
Timestamp window: off by default. Hookdeck retries and replays resend the original headers and timestamp, and Hookdeck applies no tolerance, so a fixed window would reject legitimate retries. The examples offer an opt-in CASHFREE_ (unset or 0 = off). Cashfree's docs suggest 5 minutes, but the unit of x-webhook-timestamp is not stated, so check a real delivery first.
Legacy Payouts V1 and Cashgram webhooks (form-encoded, different scheme) are not covered and fail this verification.
For complete handlers with tests, see examples/express/, examples/nextjs/, examples/fastapi/.
Common Event Types
Payment Gateway and Payouts V2 bodies carry the event in type; Secure ID and PPI bodies carry it in event_type. Read type, fall back to event_type.
| Event | Product | Fired when |
|---|---|---|
PAYMENT_SUCCESS_WEBHOOK | Payment Gateway | A payment succeeds |
PAYMENT_FAILED_WEBHOOK | Payment Gateway | A payment fails |
PAYMENT_ | Payment Gateway | The customer abandons the payment |
REFUND_STATUS_WEBHOOK | Payment Gateway | A refund changes status |
SETTLEMENT_ | Payment Gateway | A settlement changes status |
TRANSFER_SUCCESS / TRANSFER_FAILED / TRANSFER_REVERSED | Payouts V2 | A transfer succeeds, fails or is reversed |
BANK_ (e.g. _SUCCESS), VKYC_* (e.g. VKYC_) | Secure ID | Verification and Video KYC progress |
PPI_CREDIT_SUCCESS | PPI | A wallet credit succeeds |
The overview page's samples abbreviate some Payment Gateway names (e.g. PAYMENT_SUCCESS), so handle both spellings. More: references/overview.md.
Delivery Semantics
- Respond HTTP 200. Anything else, 3xx included, fails and is retried.
- Payment Gateway: per-endpoint policy (default 3 retries at 2, 10 and 30 minutes, or Fixed/Exponential/Custom up to 10) and Batch Resend in the dashboard. Payouts V2: fixed schedule, endpoint disabled after 5 consecutive failures.
- At-least-once, unordered: dedupe on business IDs (
cf_payment_id,transfer_id, ...) and process asynchronously. - Hookdeck's Cashfree source manages the method as
POST.
Environment Variables
CASHFREE_WEBHOOK_SECRET=your_cashfree_client_secret # handlers fail closed (500) without it
# CASHFREE_WEBHOOK_TOLERANCE_SECONDS=0 # optional replay window; 0/unset = off
Local Development
# Express / Next.js (use 8000 for FastAPI). No install or account required.
npx hookdeck-cli listen 3000 cashfree --path /webhooks/cashfree
Register the printed URL as a webhook endpoint in the Cashfree Merchant Dashboard. See references/setup.md.
Reference Materials
- references/overview.md - Products, event catalog, payload shapes, retries
- references/setup.md - Hookdeck source setup, client secret, endpoint registration
- references/verification.md - Algorithm, manual verification, gotchas, debugging