Key concepts
The following are the key concepts:-
Multiple payment attempts per order_id: For a single
order_id
, Cashfree allows multiple payment attempts until one succeeds. Each attempt generates a uniquecf_payment_id
. -
Unique payment_id per attempt: Every retry—whether triggered by the user or automatically—will have a distinct
cf_payment_id
but the sameorder_id
. -
Terminal status: Only consider webhook events where
payment_status = SUCCESS
as the final confirmation of payment for an order.
Statuses such as
FAILED
, NOT_ATTEMPTED
, or PENDING
are transitional and should not be treated as final.
Webhook idempotency flowchart
Best practices
The following are the recommended best practices:-
De-duplicate using payment_id: Track
cf_payment_id
in your database and ensure it is processed only once, regardless of how many webhook retries you receive. -
Order-level validation: Update the order status only when you receive a webhook that meets all of the following conditions:
- The
order_id
matches the one you created. - The
payment_status
isSUCCESS
. - The order has not already been marked as paid.
- The
-
Handle retried payments: Users may retry failed payments. As a result, you may receive multiple webhook events in the sequence:
FAILED → PENDING → SUCCESS
.
Implement logic to update the order status only upon receiving theSUCCESS
webhook. -
Ignore duplicate FAILED webhooks: Webhook events with a
FAILED
status may be sent multiple times. Do not trigger any final actions for these statuses unless you are tracking them for logging or metrics. -
Store payment attempts: Keep a log of all
cf_payment_id
for a givenorder_id
to support reporting, reconciliation, or dispute management.
Do not mark the order as PAID on receiving
FAILED
or PENDING
statuses.
Always wait for a SUCCESS
webhook before finalising the payment status.