Webhook Handling
PhonePe Payment Gateway uses Webhook (S2S Callbacks) to notify you about key events like payment completion or refund status. Here’s how it works:
- You need to provide a Webhook URL (a specific endpoint on your server) where these updates will be sent.
- To ensure secure communication, you should set up a username and password for authentication.
Webhook Setup
- Configure Webhook: Follow these steps to set up a new webhook for receiving event notifications.
- Log in to your PhonePe Business Dashboard.Set the environment mode using the Test Mode toggle located on the dashboard.
- For Sandbox (Testing): Ensure the toggle is switched ON.
- For Production (Live): Ensure the toggle is switched OFF.
- Navigate to Developer Settings from the side menu.Select the Webhook tab and click the Create Webhook button.In the configuration form, fill in the following details:
- Webhook URL: Your server’s endpoint URL to receive notifications.
- Username: Your authentication username.Password: Your authentication password.
- Description: A brief description for your reference.
- From the list of active events, choose:
checkout.order.completed: Sent when an order is successfully completed: Sent when an order failscheckout.order.failed
- Click Create to save and complete the configuration.
- Your webhook is now active.
- Log in to your PhonePe Business Dashboard.Set the environment mode using the Test Mode toggle located on the dashboard.
- Authorization.
- Once configured, PhonePe Payment Gateway will send updates to your server using the provided username and password.
- These credentials will be used to create an Authorization header in the webhook response using SHA256 (username:password) method.
- Verification.
- For the incoming request, extract the header Authorization, verify it with the one which you have shared with us and accept the response if the Username and password match.
- If the hash matches the one sent by PhonePe Payment Gateway, the update is valid, and the response payload can be consumed.
- If it doesn’t match, the response should be ignored.
ℹ️ Authorization Header!
PhonePe Payment Gateway includes the Authorization header in the following format:
Authorization: SHA256(username:password)
Callback Validation/Verification
- For the incoming request, extract the basic authorization header “Authorization”, verify it with the one which you have shared with us and accept the response if Username and password matches.
- Use the “payload.state” Parameter: For payment status, rely only on the root-level “payload.state” field in the response
- Avoid Strict Deserialization: Don’t use overly strict rules for processing the response
- Use the “event” Parameter: Ignore the “type” parameter in the webhook response. Use the “event” parameter instead to identify the event type
- Time Format: The expireAt and timestamp fields will be in epoch time
Webhook Responses
Response for Order Completed
{
"type": "CHECKOUT_ORDER_COMPLETED",
"event": "checkout.order.completed",
"payload": {
"merchantId": "PRODTEST",
"merchantOrderId": "TEST_ORD76218257999841196127",
"orderId": "OMO2512091216567658772255V",
"state": "COMPLETED",
"currency": "INR",
"amount": 200,
"payableCurrency": "INR",
"payableAmount": 200,
"feeCurrency": "INR",
"feeAmount": 0,
"expireAt": 1765264016765,
"metaInfo": {
"udf1": "some meta info of max length 256",
"udf2": "some meta info of max length 256",
"udf3": "some meta info of max length 256",
"udf4": "some meta info of max length 256",
"udf5": "some meta info of max length 256",
"udf6": "some meta info of max length 256",
"udf7": "some meta info of max length 256",
"udf8": "some meta info of max length 256",
"udf10": "some meta info of max length 256",
"udf11": "some meta info of max length 50",
"udf12": "some meta info of max length 50",
"udf13": "some meta info of max length 50",
"udf14": "some meta info of max length 50",
"udf15": "some meta info of max length 50"
},
"paymentFlow": {
"type": "SUBSCRIPTION_CHECKOUT_SETUP",
"merchantSubscriptionId": "MSUB_5580745967290798888",
"authWorkflowType": "PENNY_DROP",
"amountType": "VARIABLE",
"maxAmount": 200,
"frequency": "DAILY",
"expireAt": 2711947616753,
"subscriptionId": "OMS2512091216567538772793V"
},
"paymentDetails": [
{
"transactionId": "OM2512091217085453615241V",
"paymentMode": "UPI_QR",
"timestamp": 1765262828576,
"currency": "INR",
"amount": 200,
"payableCurrency": "INR",
"payableAmount": 200,
"feeCurrency": "INR",
"feeAmount": 0,
"state": "COMPLETED",
"instrument": {
"type": "ACCOUNT",
"ifsc": "KKBK*****24",
"accountHolderName": "HRISHIKESH RAVINDRA PARAMANE",
"accountType": "SAVINGS",
"bankId": "KKBK"
},
"rail": {
"type": "UPI",
"utr": "534312158356",
"vpa": "12****78@ybl",
"umn": "75103a529f684f1cb9fac5ae7de4edb8@ybl"
},
"splitInstruments": [
{
"instrument": {
"type": "ACCOUNT",
"ifsc": "KKBK0000724",
"accountHolderName": "RAVINDRA ",
"accountType": "SAVINGS",
"bankId": "KKBK"
},
"rail": {
"type": "UPI",
"utr": "534312158356",
"vpa": "12****78@ybl",
"umn": "75103a529f684f1cb9fac5ae7de4edb8@ybl"
},
"currency": "INR",
"amount": 200
}
]
}
]
}
}Response for Order Failed
{
"type": "CHECKOUT_ORDER_FAILED",
"event": "checkout.order.failed",
"payload": {
"merchantId": "PRODTEST",
"merchantOrderId": "TEST_ORD92782488154686223748",
"orderId": "OMO2512091220426054140059V",
"state": "FAILED",
"currency": "INR",
"amount": 200,
"expireAt": 1765264242605,
"errorCode": "OTHERS",
"detailedErrorCode": "INTENT_EXPIRED",
"metaInfo": {
"udf1": "some meta info of max length 256",
"udf2": "some meta info of max length 256",
"udf3": "some meta info of max length 256",
"udf4": "some meta info of max length 256",
"udf5": "some meta info of max length 256",
"udf6": "some meta info of max length 256",
"udf7": "some meta info of max length 256",
"udf8": "some meta info of max length 256",
"udf10": "some meta info of max length 256",
"udf11": "some meta info of max length 50",
"udf12": "some meta info of max length 50",
"udf13": "some meta info of max length 50",
"udf14": "some meta info of max length 50",
"udf15": "some meta info of max length 50"
},
"paymentFlow": {
"type": "SUBSCRIPTION_CHECKOUT_SETUP",
"merchantSubscriptionId": "MSUB_5843827890811415656",
"authWorkflowType": "PENNY_DROP",
"amountType": "VARIABLE",
"maxAmount": 200,
"frequency": "DAILY",
"expireAt": 2711947842592,
"subscriptionId": "OMS2512091220425914140379V"
},
"paymentDetails": [
{
"transactionId": "OM2512091225260208772899V",
"paymentMode": "UPI_QR",
"timestamp": 1765263326040,
"currency": "INR",
"amount": 200,
"payableCurrency": "INR",
"payableAmount": 200,
"feeCurrency": "INR",
"feeAmount": 0,
"state": "FAILED",
"errorCode": "OTHERS",
"detailedErrorCode": "INTENT_EXPIRED"
}
]
}
}What if Webhook fails?
If you don’t receive the Webhook callback, you can use the Order Status to manually check the payment status.