Webhook Handling
PhonePe Payment Gateway uses Webhooks (S2S Callbacks) to notify your server about key events, such as payment completion or refund statuses. By providing a specific Webhook URL on your server, PhonePe can push real-time updates directly to your system.
Webhook Setup & Configuration
You can configure your webhook using one of two authentication methods. Both methods follow the same initial setup but require slightly different details on the configuration form.
- Using HMAC Authentication: A secure approach that allows the receiver to verify both the authenticity and integrity of the payload using a shared secret key.
- Using Basic Authentication (SHA): A method where a configured username and password are hashed (SHA-256) and sent in the authorization header.
Configuration Steps
Follow these steps to set up your webhook:
- Log in to your PhonePe Business Dashboard.
- Set the Environment Mode using the Test Mode toggle on the dashboard:
- Sandbox (Testing): Switch the toggle ON.
- Production (Live): Switch the toggle OFF.
- Navigate to Developer Settings from the side menu.
- Select the Webhook tab and click the Create Webhook button.
- Fill in the Configuration Details based on your preferred authentication type:
- Option A: HMAC
- Select Authentication Type as HMAC.
- Provide your Webhook URL (your server’s endpoint), select the Events you wish to subscribe to, and add a brief Description.
- Secret Key Generation: Once you click create, a Secret Key will be automatically generated. You can view and access this key at any time by clicking on the created webhook in your dashboard.
- Editing the Webhook: You can edit an existing webhook at a later time; however, you will only be able to update the Webhook URL, selected Events, and Description.
- Option B: SHA (Username & Password)
- Select Authentication Type as SHA.
- Provide your Webhook URL, Username, Password, and a brief Description.
- Select Active Events.
- Option A: HMAC
- Click Create to save. Your webhook is now active!
Authorization & Verification
How you verify incoming webhooks depends on the method you chose:
If you chose HMAC:
- Credentials: Upon creation of the HMAC webhook, a Checksum Secret Key and a Webhook ID will be generated. You must save these securely on your end.
- Authorization: During the transactional flow, PhonePe encrypts the plain text payload using the secret key and transmits two specific parameters in the headers:
- The generated checksum X-phonepe-checksum-key-id
- The phonepe-checksum-signature value.
Verification:
- When your server receives the plain text payload, extract the x-phonepe-checksum-key-id and x-phonepe-checksum-signature headers.
- Use the x-phonepe-checksum-key-id to identify and retrieve the specific Checksum Secret Key you saved earlier.
- Generate your own checksum by encrypting the received plain text payload with that Secret Key.
- Compare your generated checksum against the x-phonepe-checksum-signature header. If they match, the payload is valid and authentic. If they do not match, you must reject the payload.
If you chose SHA:
- Authorization: PhonePe will use your configured credentials to create an Authorization header in the webhook response using the SHA256(username:password) method.
- Verification: For incoming requests, extract the Authorization header. Generate your own hash using the username and password you configured and compare it. If the hashes match, the update is valid. If they do not match, the response should be ignored.
Supported Events
Select only the specific events you need to reduce unnecessary server load and processing “Events.”
| Event Name | Trigger Condition |
checkout.order.completed | Sent when an order is successfully completed. |
checkout.order.failed | Sent when an order fails. |
pg.refund.completed | Sent when a refund is successfully processed. |
pg.refund.failed | Sent when refund processing fails. |
Security & Validation Best Practices
To ensure secure and reliable communication between PhonePe and your server, adhere to the following standards:
- HTTPS Requirement: All listener URLs must strictly use HTTPS with valid SSL/TLS certificates.
- Acknowledge Promptly: Your listener must return a 2xx status code (e.g., 200 OK) within 3-5 seconds to prevent timeouts.
- Idempotency: It is possible for the same transaction webhook to trigger multiple times. Implement logic in your system to safely handle duplicate events.
- Time Format: The expireAt and timestamp fields in payloads will be provided in epoch time.
- Avoid Strict Deserialization: Do not use overly strict rules for processing the response to prevent breaking your integration if non-breaking fields are added in the future.
- Use the event Parameter: Ignore the type parameter in the webhook response. Use the event parameter to identify the event type.
- Use the payload.state Parameter: For payment status, rely only on the root-level payload.state field in the response.
IP Whitelisting
If your server restricts incoming traffic, you must configure your network to allow traffic from our specific proxy IPs.
ℹ️ IP Whitelisting!
If there are any server restrictions on receiving the Webhooks, please find below the list of the IP’s to be whitelisted to receive the webhook responses on your server:
103.116.33.8
103.116.33.9
103.116.33.10
103.116.33.11
103.116.33.136
103.116.33.137
103.116.33.138
103.116.33.139
103.116.32.16
103.116.32.17
103.116.32.18
103.116.32.19
103.116.32.20
103.116.32.21
103.116.32.22
103.116.32.23
103.116.32.24
103.116.32.25
103.116.32.26
103.116.32.27
103.116.32.28
103.116.32.29
103.116.34.1
103.116.34.16
103.116.34.17
103.116.34.18
103.116.34.19
103.116.34.20
103.116.34.21
103.116.34.22
103.116.34.23
103.243.35.242
Outbound IP CIDR
103.116.34.16/29
103.116.32.16/29
103.116.33.8/30
103.116.33.136/30
Webhook Responses
{
"event": "checkout.order.completed",
"payload": {
"orderId": "OMO2403282020198641071317",
"merchantId": "merchantId",
"merchantOrderId": "merchantOrderId",
"state": "COMPLETED",
"amount": 10000,
"expireAt": 1724866793837,
"paymentDetails": [
{
"paymentMode": "UPI_QR",
"transactionId": "OM12334",
"timestamp": 1724866793837,
"amount": 10000,
"state": "COMPLETED"
}
],
"metaInfo": {
"udf1": "",
"udf2": "",
"udf3": "",
"udf4": "",
"udf5": "",
"udf6": "",
"udf7": "",
"udf8": "",
"udf9": "",
"udf10": "",
"udf11": "",
"udf12": "",
"udf13": "",
"udf14": "",
"udf15": ""
}
}
}{
"event": "checkout.order.failed",
"payload": {
"orderId": "OMO2403282020198641071311",
"merchantId": "merchantId",
"merchantOrderId": "merchantOrderId",
"state": "FAILED",
"amount": 10000,
"expireAt": 1724866793837,
"paymentDetails": [
{
"paymentMode": "UPI_COLLECT",
"timestamp": 1724866793837,
"amount": 10000,
"transactionId": "OM12333",
"state": "FAILED",
"errorCode": "AUTHORIZATION_ERROR",
"detailedErrorCode": "ZM"
}
],
"metaInfo": {
"udf1": "",
"udf2": "",
"udf3": "",
"udf4": ""
}
}
}Response for Refund Completed
{
"originalMerchantOrderId": "Order123",
"amount": 100,
"state": "COMPLETED",
"timestamp": 1730869961754,
"refundId": "OMR7878098045517540996",
"errorCode": "", // Only present in case of ERROR
"detailedErrorCode": "", // Only present in case of ERROR
"splitInstruments": [
{
"amount": 100,
"rail": {
"type": "UPI",
"utr": "586756785",
"upiTransactionId": "YBL5bc011fa9f8644763b52b96a29a9655",
"vpa": "12****78@ybl"
},
"instrument": {
"type": "ACCOUNT",
"maskedAccountNumber": "******1234",
"accountType": "SAVINGS"
}
}
]
}{
"event": "pg.refund.completed",
"payload": {
"merchantId": "merchantId",
"merchantRefundId": "merchantRefundId",
"originalMerchantOrderId": "Refund-12345",
"amount": 50000,
"state": "COMPLETED",
"timestamp": 1730869961754,
"refundId": "OMR7878098045517540996",
"paymentDetails": [
{
"paymentMode": "UPI_INTENT",
"timestamp": 1706629419799,
"amount": 50000,
"transactionId": "OMR7896789",
"state": "COMPLETED"
}
]
}
}{
"originalMerchantOrderId": "TX1752742884088",
"amount": 50,
"state": "COMPLETED",
"refundId": "OMR2507211657281836129569",
"timestamp": 1753097248198,
"splitInstruments": [
{
"instrument": {
"type": "NET_BANKING",
"bankId": "SBIN",
"brn": "brn123"
},
"rail": {
"type": "PG"
},
"amount": 50
}
],
"paymentDetails": [
{
"transactionId": "OMR2507211657281836129569",
"paymentMode": "NET_BANKING",
"timestamp": 1753097248198,
"amount": 50,
"state": "COMPLETED",
"instrument": {
"type": "NET_BANKING",
"bankId": "SBIN"
},
"rail": {
"type": "PG"
},
"splitInstruments": [
{
"instrument": {
"type": "NET_BANKING",
"bankId": "SBIN"
},
"rail": {
"type": "PG"
},
"amount": 50
}
]
}
]
}Response for Refund Failed
{
"event": "pg.refund.failed",
"payload": {
"originalMerchantOrderId": "",
"refundId": "OMRxxxxx",
"amount": 1234,
"state": "FAILED",
"timestamp": 1730869961754,
"refundId": "OMR7878098045517540996",
"errorCode": "AUTHORIZATION_ERROR",
"detailedErrorCode": "ZM",
"paymentDetails": [
{
"paymentMode": "UPI_INTENT",
"timestamp": 1706629419799,
"amount": 50000,
"transactionId": "OMR7896789",
"state": "FAILED",
"splitInstruments": [
{
"amount": 50000,
"rail": {
"type": "UPI",
"upiTransactionId": "upi12313",
"vpa": "12****78@ybl"
},
"instrument": {
"type": "ACCOUNT",
"accountType": "SAVINGS",
"accountNumber": "******1234"
}
}
]
}
]
}
}{
"event": "pg.refund.failed",
"payload": {
"originalMerchantOrderId": "",
"refundId": "OMRxxxxx",
"amount": 1234,
"state": "FAILED",
"timestamp": 1730869961754,
"refundId": "OMR7878098045517540996",
"errorCode": "AUTHORIZATION_ERROR",
"detailedErrorCode": "ZM",
}
}{
"event": "pg.refund.failed",
"payload": {
"originalMerchantOrderId": "",
"refundId": "OMRxxxxx",
"amount": 1234,
"state": "FAILED",
"timestamp": 1730869961754,
"refundId": "OMR7878098045517540996",
"errorCode": "AUTHORIZATION_ERROR",
"detailedErrorCode": "ZM"
}
}What if Webhook fails?
If you don’t receive the Webhook callback, you can use the Order Status to manually check the payment status.
What’s Next?
Now that you have learned how to verify the payment and what happens when the webhook fails, this concludes your website integration. The next step is to complete UAT testing and understand the process to go live.