Configuring Webhooks

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.
  • Configure Webhook.
    • Production: You can configure the Webhook URL, username, and password directly on the PhonePe Business dashboard.
    • Sandbox: You must reach out to our Integration Team to set this up. To do so, 
      • Click Help in the side panel of your dashboard.
      • Select Integration and click Contact Us.
      • Share the webhook callback URL while creating the ticket.
  • 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.
  • Order Events:
    • checkout.order.completed: Sent when an order is successfully completed
    • checkout.order.failed: Sent when an order fails
  • Refund Events:
    • pg.refund.completed: Sent when a refund is successfully processed
    • pg.refund.failed: Sent when a refund processing fails

Important: PhonePe Payment Gateway includes the Authorization header in the following format:
Authorization: SHA256(username:password)

  • 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
Response for Order Completed
{
    "event": "pg.order.completed",
    "payload": {
        "orderId": "OMOxx",
        "merchantId": "merchantId",
        "merchantOrderId": "merchantOrderId",
        "state": "COMPLETED",
        "amount": 10000,
        "expireAt": 1291391291,
        "metaInfo": {
            "udf1": "",
            "udf2": "",
            "udf3": "",
            "udf4": ""
        },
        "paymentDetails": [
            {
                "paymentMode": "UPI_QR",
                "transactionId": "OM12334",
                "timestamp": 1724866793837,
                "amount": 10000,
                "state": "COMPLETED",
                "splitInstruments": [
                    {
                        "amount": 10000,
                        "rail": {
                            "type": "UPI",
                            "upiTransactionId": "upi12313",
                            "vpa": "abcd@ybl"
                        },
                        "instrument": {
                            "type": "ACCOUNT",
                            "accountType": "SAVINGS",
                            "accountNumber": "121212121212"
                        }
                    }
                ]
            }
        ]
    }
}
Response for Order Failed
{
    "event": "pg.order.failed",
    "payload": {
        "orderId": "OMOxx",
        "merchantId": "merchantId",
        "merchantOrderId": "merchantOrderId",
        "state": "EXPIRED",
        "amount": 10000,
        "expireAt": 1724866793837,
        "metaInfo": {
            "udf1": "",
            "udf2": "",
            "udf3": "",
            "udf4": ""
        },
        "paymentDetails": [
            {
                "paymentMode": "UPI_COLLECT",
                "timestamp": 1724866793837,
                "amount": 10000,
                "transactionId": "OM12333",
                "state": "FAILED",
                "errorCode": "AUTHORIZATION_ERROR",
                "detailedErrorCode": "ZM",
                "splitInstruments": [
                    {
                        "amount": 10000,
                        "rail": {
                            "type": "UPI",
                            "upiTransactionId": "upi12313",
                            "vpa": "abcd@ybl"
                        },
                        "instrument": {
                            "type": "ACCOUNT",
                            "accountType": "SAVINGS",
                            "accountNumber": "121212121212"
                        }
                    }
                ]
            }
        ]
    }
}
Case 1: Original source of transaction = UPI
{
    "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",
                "splitInstruments": [
                    {
                        "amount": 50000,
                        "rail": {
                            "type": "UPI",
                            "upiTransactionId": "upi12313",
                            "vpa": "abcd@ybl"
                        },
                        "instrument": {
                            "type": "ACCOUNT",
                            "accountType": "SAVINGS",
                            "accountNumber": "121212121212"
                        }
                    }
                ]
            }
        ]
    }
}
Case 2: Original source of transaction = CARD
{
    "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",
                "splitInstruments": [
                    {
                        "amount": 50000,
                        "rail": {
                            "type": "PG",
                            "transactionId": "transactionId",
                            "authorizationCode": "authorizationCode",
                            "serviceTransactionId": "serviceTransactionId"
                        },
                        "instrument": {
                            "type": "CREDIT_CARD",
                            "bankTransactionId": "bankTransactionId",
                            "bankId": "bankId",
                            "arn": "arn",
                            "brn": "brn"
                        }
                    }
                ]
            }
        ]
    }
}
Case 3: Original source of transaction = NET_BANKING
{
    "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
                }
            ]
        }
    ]
}
Case 1: Original source of transaction = UPI
{
    "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": "COMPLETED",
                "splitInstruments": [
                    {
                        "amount": 50000,
                        "rail": {
                            "type": "UPI",
                            "upiTransactionId": "upi12313",
                            "vpa": "abcd@ybl"
                        },
                        "instrument": {
                            "type": "ACCOUNT",
                            "accountType": "SAVINGS",
                            "accountNumber": "121212121212"
                        }
                    }
                ]
            }
        ]
    }
}
Case 2: Original source of transaction = CARD
{
    "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": "COMPLETED",
                "splitInstruments": [
                    {
                        "amount": 50000,
                        "rail": {
                            "type": "PG",
                            "transactionId": "transactionId",
                            "authorizationCode": "authorizationCode",
                            "serviceTransactionId": "serviceTransactionId"
                        },
                        "instrument": {
                            "type": "CREDIT_CARD",
                            "bankTransactionId": "bankTransactionId",
                            "bankId": "bankId",
                            "arn": "arn",
                            "brn": "brn"
                        }
                    }
                ]
            }
        ]
    }
}
Case 3: Original source of transaction = NET_BANKING
{
    "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": "COMPLETED",
                "splitInstruments": [
                    {
                        "amount": 50000,
                        "rail": {
                            "type": "PG",
                            "transactionId": "transactionId",
                            "authorizationCode": "authorizationCode",
                            "serviceTransactionId": "serviceTransactionId"
                        },
                        "instrument": {
                            "type": "NET_BANKING",
                            "bankTransactionId": "bankTransactionId",
                            "bankId": "bankId",
                            "arn": "arn",
                            "brn": "brn"
                        }
                    }
                ]
            }
        ]
    }
}

If you don’t receive the Webhook callback, you can use the Order Status API to manually check the payment status.

Is this article helpful?