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_WEBHOOK or TRANSFER_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)

ItemValue
Signature headerx-webhook-signature: bare standard base64 (padded, 44 chars), no prefix
Timestamp headerx-webhook-timestamp
AlgorithmHMAC-SHA256
Signed contentthe x-webhook-timestamp value, then the raw body, no separator
KeyCashfree 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_body, hashlib.sha256).digest(), 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_WEBHOOK_TOLERANCE_SECONDS (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.

EventProductFired when
PAYMENT_SUCCESS_WEBHOOKPayment GatewayA payment succeeds
PAYMENT_FAILED_WEBHOOKPayment GatewayA payment fails
PAYMENT_USER_DROPPED_WEBHOOKPayment GatewayThe customer abandons the payment
REFUND_STATUS_WEBHOOKPayment GatewayA refund changes status
SETTLEMENT_STATUS_WEBHOOKPayment GatewayA settlement changes status
TRANSFER_SUCCESS / TRANSFER_FAILED / TRANSFER_REVERSEDPayouts V2A transfer succeeds, fails or is reversed
BANK_ACCOUNT_VERIFICATION_* (e.g. _SUCCESS), VKYC_* (e.g. VKYC_AUDITOR_REVIEW_COMPLETED)Secure IDVerification and Video KYC progress
PPI_CREDIT_SUCCESSPPIA 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


Repository

hookdeck/webhook-skills

v0.1.0 · MIT · Updated Nov 9, 2026

View on GitHub →