Skip to content

Payouts in Cashfree

This document provides a detailed description for creating beneficiaries used for integrating with Cashfree Payouts - Version 2 APIs, including data flow, constraints, validation, and backend responsibilities.


When creating a beneficiary using Cashfree APIs, the system must:

  1. Collect details from frontend.
  2. Validate and store the data internally.
  3. Generate a beneficiary_id.
  4. Prepare the Cashfree-compliant payload.
  5. Send the API request and store the returned status and metadata.

A two-table architecture where:

  • users table stores your application’s user identity data.
  • beneficiaries table stores payout-related bank/UPI details.

This separation supports:

  • Multiple beneficiaries per user.
  • Cleaner user profile management.
  • Better audit logging and traceability.

Step 1 — Frontend Input (Raw User Inputs)

Section titled “Step 1 — Frontend Input (Raw User Inputs)”
{
"beneficiary_name": "",
"beneficiary_phone": "",
"beneficiary_email": "",
"beneficiary_country_code": "",
"beneficiary_address": "",
"beneficiary_city": "",
"beneficiary_state": "",
"beneficiary_postal_code": "",
"bank_account_number": "",
"bank_ifsc": "",
"vpa": ""
}

Step 2 — Backend Validates & Stores Preliminary Data

Section titled “Step 2 — Backend Validates & Stores Preliminary Data”
  • Validate formats (email, phone, IFSC, account number).
  • Create DB entry with status = PENDING.

Mapped fields:

  • Personal details → beneficiary_contact_details
  • Bank/UPI details → beneficiary_instrument_details
  • System-generated → beneficiary_id

POST /payout/beneficiary

Save:

  • beneficiary_status
  • added_on
  • Error fields if applicable

ColumnTypeDescriptionNotes
idBIGINT PKInternal unique user IDAuto-increment
nameVARCHARUser’s display nameFrom frontend/user account
emailVARCHARUser emailUnique if needed
phoneVARCHARUser phoneOptional uniqueness
country_codeVARCHARCountry dialing codeDefault +91
created_atTIMESTAMPRow creation timeAuto
updated_atTIMESTAMPRow update timeAuto

The users table represents your platform’s real user accounts. Beneficiaries belong to users but are independent payout entities.


ColumnTypeDescriptionNotes
idBIGINT PKInternal primary keyAuto-increment
user_idBIGINT FKFK → users.idRequired
beneficiary_idVARCHAR UNIQUEUnique ID sent to CashfreeMust never change once created
beneficiary_nameVARCHARPerson/entity receiving payoutFrom frontend
bank_account_numberVARCHARBank account numberOptional if VPA used
bank_ifscVARCHARIFSC codeValidate with regex
vpaVARCHARUPI IDOptional if bank details used
emailVARCHARBeneficiary emailRequired by Cashfree
phoneVARCHARBeneficiary phoneRequired
country_codeVARCHAR+91 etcDefault from user
addressTEXTBeneficiary addressRequired
cityVARCHARRequired
stateVARCHARRequired
postal_codeVARCHARRequired
beneficiary_statusVARCHARStatus from CashfreeVERIFIED / PENDING / FAILED
added_onTIMESTAMPFrom Cashfree responseSave as-is
last_error_codeVARCHAROptionalUseful for debugging
last_error_messageTEXTOptionalUseful for debugging
raw_responseJSONOptional full Cashfree payloadHelpful for audits
created_atTIMESTAMPAuto
updated_atTIMESTAMPAuto
  • At least one of the following must exist:
    • bank_account_number + bank_ifsc
    • vpa
  • The backend should enforce that one payout instrument is present.
  • beneficiary_id must be unique, stable, deterministic.

  1. Save beneficiary → status = PENDING.
  2. Call Cashfree.
  3. Update:
    • beneficiary_status
    • added_on
    • last_error_code / message

Store:

last_error_code
last_error_message

Mark status as:

FAILED

If frontend retries:

  • Do not create duplicate beneficiaries.
  • Check:
    • beneficiary_id
    • OR (user_id, bank_account_number, bank_ifsc) combination

{
"beneficiary_id": "<generated>",
"beneficiary_name": "<name>",
"beneficiary_instrument_details": {
"bank_account_number": "...",
"bank_ifsc": "...",
"vpa": "..."
},
"beneficiary_contact_details": {
"beneficiary_email": "...",
"beneficiary_phone": "...",
"beneficiary_country_code": "+91",
"beneficiary_address": "...",
"beneficiary_city": "...",
"beneficiary_state": "...",
"beneficiary_postal_code": "..."
}
}
{
"beneficiary_id": "BEN_123_ABC",
"beneficiary_name": "John Doe",
"beneficiary_instrument_details": {
"bank_account_number": "1223334444",
"bank_ifsc": "HDFC0000001",
"vpa": "test@upi"
},
"beneficiary_contact_details": {
"beneficiary_email": "sample@cashfree.com",
"beneficiary_phone": "9876543210",
"beneficiary_country_code": "+91",
"beneficiary_address": "177A Bleecker Street",
"beneficiary_city": "New York City",
"beneficiary_state": "New York",
"beneficiary_postal_code": "560011"
},
"beneficiary_status": "VERIFIED",
"added_on": "2023-12-04T15:50:00Z"
}

This document provides a detailed description for initiating standard transfers using Cashfree Payouts - Version 2 APIs, including data flow, constraints, validation, and backend responsibilities.


When initiating a standard transfer using Cashfree APIs, the system must:

  1. Collect transfer details from frontend (amount, beneficiary, transfer mode).
  2. Validate the data internally (amount, beneficiary status, fund source).
  3. Generate a unique transfer_id.
  4. Prepare the Cashfree-compliant payload.
  5. Send the API request (async by default).
  6. Store the returned status and metadata.
  7. Poll or use webhooks to track transfer status updates.

Key Characteristics:

  • Async by default: The API returns immediately with a status, but the transfer processes asynchronously.
  • Status tracking: Use Get Transfer Status V2 API or webhooks to monitor transfer progress.
  • Idempotency: Use unique transfer_id to prevent duplicate transfers.
  • Multiple transfer modes: Supports banktransfer, IMPS, NEFT, RTGS, UPI, Paytm, AmazonPay, and card transfers.

Step 1 — Frontend Input (Raw User Inputs)

Section titled “Step 1 — Frontend Input (Raw User Inputs)”
{
"transfer_amount": 1000.0,
"beneficiary_id": "BEN_123_ABC",
"transfer_mode": "banktransfer",
"transfer_remarks": "Payment for services",
"fundsource_id": "FUND_001"
}

Alternative: If beneficiary is not pre-created, include full beneficiary details inline.

Step 2 — Backend Validates & Stores Preliminary Data

Section titled “Step 2 — Backend Validates & Stores Preliminary Data”
  • Validate transfer amount (>= 1.00).
  • Verify beneficiary exists and is VERIFIED.
  • Check fund source availability.
  • Validate transfer mode compatibility with beneficiary instrument.
  • Create DB entry with status = PENDING.

Mapped fields:

  • Transfer details → transfer_id, transfer_amount, transfer_currency, transfer_mode, transfer_remarks
  • Beneficiary reference → beneficiary_id (if pre-created) OR full beneficiary_details object
  • Fund source → fundsource_id

POST /payout/transfers

Headers:

  • x-api-version: 2024-01-01 (or latest)
  • x-client-id: <api-key>
  • x-client-secret: <api-key>
  • Content-Type: application/json

Save:

  • cf_transfer_id (Cashfree’s unique transfer ID)
  • status (initial status from response)
  • transfer_utr (if available)
  • transfer_service_charge
  • transfer_service_tax
  • added_on, updated_on
  • Poll Get Transfer Status V2 API periodically, OR
  • Use webhooks to receive status updates automatically.

ColumnTypeDescriptionNotes
idBIGINT PKInternal primary keyAuto-increment
user_idBIGINT FKFK → users.idRequired
beneficiary_idBIGINT FKFK → beneficiaries.idRequired
transfer_idVARCHAR UNIQUEUnique ID sent to CashfreeMust never change once created
cf_transfer_idVARCHARCashfree’s transfer IDFrom Cashfree response
transfer_amountDECIMAL(18,2)Transfer amount>= 1.00
transfer_currencyVARCHARCurrency codeDefault: INR
transfer_modeVARCHARTransfer modebanktransfer/imps/neft/rtgs/upi/paytm/amazonpay/card
transfer_remarksVARCHARTransfer remarksMax 70 characters
fundsource_idVARCHARFund source IDFrom Cashfree
statusVARCHARTransfer statusPENDING/SUCCESS/FAILED/APPROVAL_PENDING/etc
status_codeVARCHARDetailed status codeFrom Cashfree response
status_descriptionTEXTStatus descriptionFrom Cashfree response
transfer_utrVARCHARUnique Transaction ReferenceFrom bank/Cashfree
transfer_service_chargeDECIMAL(18,2)Service chargeFrom Cashfree response
transfer_service_taxDECIMAL(18,2)Service taxFrom Cashfree response
added_onTIMESTAMPTransfer creation timeFrom Cashfree response
updated_onTIMESTAMPLast update timeFrom Cashfree response
last_error_codeVARCHARError code if failedUseful for debugging
last_error_messageTEXTError message if failedUseful for debugging
raw_requestJSONFull request payloadHelpful for audits
raw_responseJSONFull Cashfree responseHelpful for audits
created_atTIMESTAMPAuto
updated_atTIMESTAMPAuto
  • transfer_id must be unique, stable, deterministic (max 40 characters, alphanumeric + underscore).
  • Link to beneficiaries table via beneficiary_id foreign key.
  • Store both request and response payloads for audit trails.
  • status field should be updated via polling or webhooks.

Transfer ModeDescriptionRequired Beneficiary Instrument
banktransferStandard bank transfer (default)bank_account_number + bank_ifsc
impsImmediate Payment Servicebank_account_number + bank_ifsc
neftNational Electronic Funds Transferbank_account_number + bank_ifsc
rtgsReal Time Gross Settlementbank_account_number + bank_ifsc
upiUPI transfervpa
paytmPaytm wallet transferPaytm-specific instrument
amazonpayAmazon Pay transferAmazon Pay-specific instrument
cardCard transfercard_details object
cardupiCard UPI transferCard or UPI instrument
  • Minimum: 1.00
  • Type: number<double>
  • Decimal values allowed
  • Maximum length: 40 characters
  • Allowed characters: Alphanumeric and underscore (_)
  • Must be unique across all transfers
  • Recommended format: TXN_<userId>_<timestamp> or TXN_<userId>_<random>
  • Maximum length: 70 characters
  • Allowed: Alphanumeric and whitespaces
  • Default: INR
  • String format
  • If beneficiary_id is provided: Beneficiary must exist and be VERIFIED in Cashfree.
  • If beneficiary_id is not provided: Full beneficiary_details object must be included in request.

  1. Validate input data (amount, beneficiary, mode).
  2. Check beneficiary status (must be VERIFIED).
  3. Verify fund source availability.
  4. Generate unique transfer_id.
  5. Save transfer → status = PENDING.
  6. Call Cashfree API.
  7. Update:
    • cf_transfer_id
    • status
    • status_code
    • status_description
    • transfer_utr (if available)
    • transfer_service_charge
    • transfer_service_tax
    • added_on, updated_on

Store:

last_error_code
last_error_message
status = FAILED

Important: On 5XX errors, do NOT retry immediately. Check status using Get Transfer Status V2 API first.

Common statuses:

  • PENDING: Transfer initiated, processing
  • SUCCESS: Transfer completed successfully
  • FAILED: Transfer failed
  • APPROVAL_PENDING: Requires manual approval (velocity check or limit breach)
  • REJECTED: Transfer was rejected

Status Codes:

  • VELOCITY_CHECK_FAILED: Transfer count to beneficiary breached limit → requires approval
  • TRANSFER_LIMIT_BREACH: Transfer amount breached limit → requires approval

If frontend retries:

  • Use the same transfer_id to prevent duplicate transfers.
  • Check if transfer_id already exists in database.
  • If exists, return existing transfer record instead of creating new one.

Since transfers are async:

  • Poll Get Transfer Status V2 API every 30-60 seconds for pending transfers.
  • Stop polling once status is SUCCESS or FAILED.
  • Alternatively, use webhooks for real-time status updates.

Outbound Request (Using Pre-created Beneficiary)

Section titled “Outbound Request (Using Pre-created Beneficiary)”
{
"transfer_id": "TXN_42_1702259812",
"transfer_amount": 1000.5,
"beneficiary_details": {
"beneficiary_id": "BEN_123_ABC"
},
"transfer_currency": "INR",
"transfer_mode": "banktransfer",
"transfer_remarks": "Payment for services rendered",
"fundsource_id": "FUND_001"
}

Outbound Request (Inline Beneficiary Details)

Section titled “Outbound Request (Inline Beneficiary Details)”
{
"transfer_id": "TXN_42_1702259812",
"transfer_amount": 1000.5,
"beneficiary_details": {
"beneficiary_name": "John Doe",
"beneficiary_instrument_details": {
"bank_account_number": "1223334444",
"bank_ifsc": "HDFC0000001"
},
"beneficiary_contact_details": {
"beneficiary_email": "john@example.com",
"beneficiary_phone": "9876543210",
"beneficiary_country_code": "+91",
"beneficiary_address": "177A Bleecker Street",
"beneficiary_city": "Mumbai",
"beneficiary_state": "Maharashtra",
"beneficiary_postal_code": "400001"
}
},
"transfer_currency": "INR",
"transfer_mode": "banktransfer",
"transfer_remarks": "Payment for services rendered",
"fundsource_id": "FUND_001"
}
{
"transfer_id": "TXN_42_1702259813",
"transfer_amount": 500.0,
"beneficiary_details": {
"beneficiary_id": "BEN_123_ABC"
},
"transfer_currency": "INR",
"transfer_mode": "upi",
"transfer_remarks": "Quick payment",
"fundsource_id": "FUND_001"
}
{
"transfer_id": "TXN_42_1702259814",
"transfer_amount": 2000.0,
"beneficiary_details": {
"beneficiary_name": "Jane Smith",
"beneficiary_instrument_details": {
"card_details": {
"card_token": "token_abc123",
"card_network_type": "VISA",
"card_cryptogram": "cryptogram_data",
"card_token_expiry": "2025-12",
"card_type": "DEBIT",
"card_token_PAN_sequence_number": "001"
}
},
"beneficiary_contact_details": {
"beneficiary_email": "jane@example.com",
"beneficiary_phone": "9876543211",
"beneficiary_country_code": "+91"
}
},
"transfer_currency": "INR",
"transfer_mode": "card",
"transfer_remarks": "Card payment",
"fundsource_id": "FUND_001"
}
{
"transfer_id": "TXN_42_1702259812",
"cf_transfer_id": "CF123456789",
"status": "PENDING",
"beneficiary_details": {
"beneficiary_id": "BEN_123_ABC",
"beneficiary_instrument_details": {
"bank_account_number": "1223334444",
"ifsc": "HDFC0000001"
}
},
"transfer_amount": 1000.5,
"transfer_service_charge": 2.5,
"transfer_service_tax": 0.45,
"transfer_mode": "banktransfer",
"transfer_utr": "UTR123456789012",
"fundsource_id": "FUND_001",
"added_on": "2023-12-04T15:50:00Z",
"updated_on": "2023-12-04T15:50:00Z"
}
{
"transfer_id": "TXN_42_1702259812",
"cf_transfer_id": "CF123456789",
"status": "APPROVAL_PENDING",
"status_code": "VELOCITY_CHECK_FAILED",
"status_description": "The transfer requires an approval as the count of transfers to a particular beneficiary etc have breached the limit.",
"beneficiary_details": {
"beneficiary_id": "BEN_123_ABC"
},
"transfer_amount": 1000.5,
"transfer_mode": "banktransfer",
"fundsource_id": "FUND_001",
"added_on": "2023-12-04T15:50:00Z",
"updated_on": "2023-12-04T15:50:00Z"
}

  • The API is async by default.
  • Initial response may show PENDING status.
  • Use Get Transfer Status V2 API or webhooks to track final status.

If status is APPROVAL_PENDING:

  • Approve or reject via Cashfree Merchant Dashboard.
  • Navigate to Transfers > Approve.
  • After approval, transfer proceeds to partner bank.
  • Do NOT initiate another transaction on 5XX response.
  • Check status using Get Transfer Status V2 API first.
  • Proceed based on actual status.
  • Recommended: Use webhooks for real-time updates.
  • Alternative: Poll Get Transfer Status V2 API periodically.
  • Update local database with status changes.
  • fundsource_id is required and identifies the account/wallet from which funds are debited.
  • Ensure fund source has sufficient balance before initiating transfer.
  • transfer_utr is generated by the bank/Cashfree.
  • Available only after transfer is processed.
  • Use for reconciliation and tracking.

Endpoint: POST /payout/transfers

Base URLs:

  • Sandbox: https://sandbox.cashfree.com/payout
  • Production: https://api.cashfree.com/payout

Required Headers:

  • x-api-version: 2024-01-01 (or latest)
  • x-client-id: <api-key>
  • x-client-secret: <api-key>
  • Content-Type: application/json

Optional Headers:

  • x-request-id: <unique-request-id> (for tech support)
  • x-cf-signature: <signature> (if IP not whitelisted)

Endpoint: GET /payout/transfers/{transfer_id}

Use this to check the current status of a transfer after initiation.


HTTP StatusStatusStatus CodeDescriptionNext Action
200APPROVAL_PENDINGVELOCITY_CHECK_FAILEDTransfer requires approval due to beneficiary transfer count limit breachApprove/reject via Merchant Dashboard → Transfers > Approve
200APPROVAL_PENDINGTRANSFER_LIMIT_BREACHTransfer requires approval due to amount limit breachApprove/reject via Merchant Dashboard → Transfers > Approve
200PENDING-Transfer initiated, processingPoll Get Transfer Status V2 or wait for webhook
200SUCCESS-Transfer completed successfullyTransfer complete, UTR available
200FAILEDVariousTransfer failedCheck status_code and status_description for details
400--Bad request (validation error)Check request payload and fix validation errors
403--Forbidden (authentication/authorization error)Verify API credentials and permissions
404--Resource not foundVerify beneficiary_id, fundsource_id, or transfer_id
500--Internal server errorDo NOT retry immediately. Check status via Get Transfer Status V2 first