Webhooks 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.
- For
- 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:
- Order Events:
pg.order.completed: Sent when an order is successfully completedpg.order.failed: Sent when an order fails
- Refund Events:
pg.refund.completed: Sent when a refund is successfully processedpg.refund.failed: Sent when a refund processing fails
- Order Events:
- Click Create to save and complete the configuration.
- Your webhook is now active.
- 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.
Webhook Event Types
- Order Events:
pg.order.completed: Sent when an order is successfully completedpg.order.failed: Sent when an order fails
- Refund Events:
pg.refund.completed: Sent when a refund is successfully processedpg.refund.failed: Sent when a refund processing fails
ℹ️ 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
Sample Response of Order Completed
{
"type": "PG_ORDER_COMPLETED",
"event": "pg.order.completed",
"payload": {
"merchantId": "AIRTELUAT",
"merchantOrderId": "MO-974-9c0084d009a8",
"orderId": "OMO2411281510176245053157",
"state": "COMPLETED",
"amount": 100,
"expireAt": 1732959621335,
"metaInfo": {
"udf5": "<additional-information-5>",
"udf3": "<additional-information-3>",
"udf4": "<additional-information-4>",
"udf1": "<additional-information-1>",
"udf2": "<additional-information-2>"
},
"paymentDetails": [
{
"paymentMode": "UPI_INTENT",
"transactionId": "OM2411281510213355053368",
"timestamp": 1732786838930,
"amount": 100,
"state": "COMPLETED",
"splitInstruments": [
{
"amount": "100",
"rail": {
"type": "UPI",
"utr": "<utr>",
"upiTransactionId": "<upiTransactionId>",
"vpa": "<vpa>"
},
"instrument": {
"type": "ACCOUNT",
"maskedAccountNumber": "<maskedAccountNumber>",
"accountType": "SAVINGS",
"accountHolderName": "<accountHolderName>",
"ifsc": "<ifsc>"
}
}
]
}
]
}
}Sample Response of Order Failed
{
"type": "PG_ORDER_FAILED",
"event": "pg.order.failed",
"payload": {
"merchantId": "AIRTELUAT",
"merchantOrderId": "MO-c68-9f96cc57a7e8",
"orderId": "OMO2411281511242605053174",
"state": "FAILED",
"amount": 100,
"errorCode": "AUTHORIZATION_ERROR",
"detailedErrorCode": "ZM",
"expireAt": 1732959688009,
"metaInfo": {
"udf5": "<additional-information-5>",
"udf3": "<additional-information-3>",
"udf4": "<additional-information-4>",
"udf1": "<additional-information-1>",
"udf2": "<additional-information-2>"
},
"paymentDetails": [
{
"paymentMode": "UPI_INTENT",
"transactionId": "OM2411281511280095053516",
"timestamp": 1732786903141,
"amount": 100,
"state": "FAILED",
"errorCode": "AUTHORIZATION_ERROR",
"detailedErrorCode": "ZM",
"splitInstruments": [
{
"amount": "100",
"rail": {
"type": "UPI",
"upiTransactionId": "<upiTransactionId>"
},
"instrument": {
"type": "ACCOUNT",
"maskedAccountNumber": "<maskedAccountNumber>",
"accountType": "SAVINGS",
"accountHolderName": "<accountHolderName>",
"ifsc": "<ifsc>"
}
}
]
}
]
}
}Sample Response of Refund Completed for Case 1: Original source of transaction = UPI
{
"event": "pg.refund.completed",
"payload": {
"merchantId": "merchantId",
"merchantRefundId": "merchantRefundId",
"originalMerchantOrderId": "Refund-12345",
"amount": 50000,
"state": "COMPLETED",
"timestamp": 1706629419799,
"refundId": "OMR7896789",
"splitInstruments": [
{
"rail": {
"type": "UPI",
"upiTransactionId": "upi12313",
"vpa": "12****78@ybl"
},
"instrument": {
"type": "ACCOUNT",
"accountType": "SAVINGS",
"maskedAccountNumber": "******1234"
},
"amount": 40000
},
{
"rail": {
"type": "WALLET"
},
"instrument": {
"type": "WALLET",
"walletId": "ABCD"
},
"amount": 10000
}
]
}
}Sample Response of Refund Completed for Case 2: Original source of transaction = CARD
{
"event": "pg.refund.completed",
"payload": {
"merchantId": "merchantId",
"merchantRefundId": "merchantRefundId",
"originalMerchantOrderId": "Refund-12345",
"amount": 50000,
"state": "COMPLETED",
"timestamp": 1706629419799,
"refundId": "OMR7896789",
"splitInstruments": [
{
"amount": 50000,
"rail": {
"type": "PG",
"transactionId": "transactionId",
"authorizationCode": "authorizationCode",
"serviceTransactionId": "serviceTransactionId"
},
"instrument": {
"type": "CREDIT_CARD",
"bankTransactionId": "bankTransactionId",
"bankId": "bankId",
"arn": "arn",
"brn": "brn"
}
}
]
}
}Sample Response of Refund Completed for Case 3: Original source of transaction = NET_BANKING
{
"event": "pg.refund.completed",
"payload": {
"merchantId": "merchantId",
"merchantRefundId": "merchantRefundId",
"originalMerchantOrderId": "Refund-12345",
"amount": 50000,
"state": "COMPLETED",
"timestamp": 1706629419799,
"refundId": "OMR7896789",
"splitInstruments": [
{
"amount": 50000,
"rail": {
"type": "PG",
"transactionId": "transactionId",
"authorizationCode": "authorizationCode",
"serviceTransactionId": "serviceTransactionId"
},
"instrument": {
"type": "NET_BANKING",
"bankTransactionId": "bankTransactionId",
"bankId": "bankId>",
"arn": "arn",
"brn": "brn"
}
}
]
}
}Sample Response of Refund Failed for Case 1: Original source of transaction = UPI
{
"event": "pg.refund.failed",
"payload": {
"merchantId": "merchantId",
"merchantRefundId": "merchantRefundId",
"originalMerchantOrderId": "Refund-12345",
"amount": 50000,
"state": "FAILED",
"timestamp": 1706629419799,
"refundId": "OMR7896789",
"errorCode": "UPI_BACKBONE_ERROR", // Only present in case of ERROR
"detailedErrorCode": "UPI_1231", // Only present in case of ERROR
"splitInstruments": [ // In case of failure this is optional. It may or may not be present.
{
"rail": {
"type": "UPI",
"upiTransactionId": "upi12313",
"vpa": "12****78@ybl"
},
"instrument": {
"type": "ACCOUNT",
"accountType": "SAVINGS",
"maskedAccountNumber": "******1234"
},
"amount": 40000
},
{
"rail": {
"type": "WALLET"
},
"instrument": {
"type": "WALLET",
"walletId": "ABCD"
},
"amount": 10000
}
]
}
}Sample Response of Refund Failed for Case 2: Original source of transaction = CARD
{
"event": "pg.refund.failed",
"payload": {
"merchantId": "merchantId",
"merchantRefundId": "merchantRefundId",
"originalMerchantOrderId": "Refund-12345",
"amount": 50000,
"state": "FAILED",
"timestamp": 1706629419799,
"refundId": "OMR7896789",
"errorCode": "PG_BACKBONE_ERROR", // Only present in case of ERROR
"detailedErrorCode": "UPI_1231", // Only present in case of ERROR
"splitInstruments": [ // In case of failure this is optional. It may or may not be present.
{
"amount": 50000,
"rail": {
"type": "PG",
"transactionId": "transactionId",
"authorizationCode": "authorizationCode",
"serviceTransactionId": "serviceTransactionId"
},
"instrument": {
"type": "CREDIT_CARD",
"bankTransactionId": "bankTransactionId",
"bankId": "bankId",
"arn": "arn",
"brn": "brn"
}
}
]
}
}Sample Response of Refund Failed for Case 3: Original source of transaction = NET_BANKING
{
"event": "pg.refund.failed",
"payload": {
"merchantId": "merchantId",
"merchantRefundId": "merchantRefundId",
"originalMerchantOrderId": "Refund-12345",
"amount": 50000,
"state": "FAILED",
"timestamp": 1706629419799,
"refundId": "OMR7896789",
"errorCode": "PG_BACKBONE_ERROR", // Only present in case of ERROR
"detailedErrorCode": "UPI_1231", // Only present in case of ERROR
"splitInstruments": [ // In case of failure this is optional. It may or may not be present.
{
"amount": 50000,
"rail": {
"type": "PG",
"transactionId": "transactionId",
"authorizationCode": "authorizationCode",
"serviceTransactionId": "serviceTransactionId"
},
"instrument": {
"type": "NET_BANKING",
"bankTransactionId": "bankTransactionId",
"bankId": "bankId",
"arn": "arn",
"brn": "brn"
}
}
]
}What’s Next?
In the next section, you will learn how to initiate a refund. This action is required when you need to return funds to a customer for a previously successful transaction, such as for a returned item or a cancelled order.