Webhook Handling


In a subscription-based system, it’s essential to confirm the outcome of every payment event. Once a payment or recurring debit is initiated, your system should quickly identify whether it was successful, failed, or is still pending. This real-time verification helps you take timely actions like activating services, updating customer access, retrying failed payments, or sending notifications.

To support this, PhonePe sends two types of webhook callbacks that your system should listen to:

  • Subscription Callbacks – These indicate the result of the first payment made when a subscription is created.
  • Redemption Callbacks – These provide the status of automated payments for ongoing subscriptions.
  • 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 your 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.

Callback Validation/Verification

For incoming requests, extract the Authorization header, validate it against the username:password you configured with us, and accept the response only if the values match.
Authorization: SHA256(username:password)

FlowCallback Type
Setupsubscription.setup.order.completed
subscription.setup.order.failed

State Change
subscription.paused
subscription.unpaused
subscription.revoked
subscription.cancelled
Notificationsubscription.notification.completed
subscription.notification.failed
Redemptionsubscription.redemption.order.completed
subscription.redemption.order.failed
subscription.redemption.transaction.completed
subscription.redemption.transaction.failed
Refundpg.refund.accepted
pg.refund.completed
pg.refund.failed

📘 Get It Right: Subscription Webhook Best Practices!


  • Always use the root-level payload.state parameter to confirm the subscription status.
  • Avoid strict deserialization of the webhook response.
  • Do not depend on the type parameter (this will be deprecated); instead, use the event parameter to identify the webhook event.
  • expireAt and timestamp values are provided in epoch time format (in milliseconds).
Subscription setup order – COMPLETED
{
    "event": "subscription.setup.order.completed",
    "payload": {
        "merchantId": "Mer2707",
        "merchantOrderId": "MO1708797962855",
        "orderId": "OMO2402242336055135042802",
        "state": "COMPLETED",
        "amount": 200,
        "expireAt": 1708798385505,
        "paymentFlow": {
            "type": "SUBSCRIPTION_SETUP",
            "merchantSubscriptionId": "MS1708797962855",
            "authWorkflowType": "TRANSACTION",
            "amountType": "FIXED",
            "maxAmount": 200,
            "frequency": "ON_DEMAND",
            "expireAt": 1741345725943,
            "subscriptionId": "OMS2502051638460659623138"
        },
        "paymentDetails": [
            {
                "transactionId": "OM2402242336055865042862",
                "paymentMode": "UPI_INTENT",
                "timestamp": 1708797965588,
                "amount": 200,
                "payableAmount": 200,
                "feeAmount": 0,
                "state": "COMPLETED",
                "instrument": {
                    "type": "ACCOUNT",
                    "maskedAccountNumber": "******1234"
                },
                "rail": {
                    "type": "UPI",
                    "utr": "405554491450",
                    "vpa": "12****78@ybl",
                    "umn": "d519347eb2374125bcad6e69a42cc13b@ybl"
                }
            }
        ]
    }
}
Subscription setup order – FAILED
{
    "event": "subscription.setup.order.failed",
    "payload": {
        "merchantId": "Mer2707",
        "merchantOrderId": "MO1708797962855",
        "orderId": "OMO2402242336055135042802",
        "state": "FAILED",
        "amount": 200,
        "expireAt": 1708798385505,
        "errorCode": "INVALID_MPIN",
        "detailedErrorCode": "ZM",
        "paymentFlow": {
            "type": "SUBSCRIPTION_SETUP",
            "merchantSubscriptionId": "MS1708797962855",
            "authWorkflowType": "TRANSACTION",
            "amountType": "FIXED",
            "maxAmount": 200,
            "frequency": "ON_DEMAND",
            "expireAt": 1741345725943,
            "subscriptionId": "OMS2502051638460659623138"
        },
        "paymentDetails": [
            {
                "transactionId": "OM2402242336055865042862",
                "paymentMode": "UPI_INTENT",
                "timestamp": 1708797965588,
                "amount": 200,
                "payableAmount": 200,
                "feeAmount": 0,
                "state": "FAILED",
                "errorCode": "INVALID_MPIN",
                "detailedErrorCode": "ZM"
            }
        ]
    }
}
Response Parameter
Parameter NameData TypeDescription
merchantIdStringA unique ID assigned to the merchant by the PhonePe system during the onboarding process.
merchantOrderIdStringA unique orderId provided by the merchant when creating the order. This helps in tracking and managing the specific transaction across systems.
orderIdStringAn orderId generated by PhonePe.
stateStringPossible value:
• COMPLETED
• FAILED
• PENDING
amountLongThe amount provided by the merchant in Paise.
expireAtDateTimeRepresents the time in epoch (milliseconds).
errorCodeStringIndicates the reason for the failure.
[PRESENT ONLY IF STATE = FAILED]
detailedErrorCodeStringProvides a detailed explanation for the failure.
MetaInfoArrayContains metadata provided by the merchant.
paymentFlowObjectProvides the detailed information of the subscription.
paymentFlow.typeStringThe type should be set to “SUBSCRIPTION_REDEMPTION” for redemption transactions.
paymentFlow.merchantSubscriptionIdStringA unique subscriptionId provided by the merchant to identify and manage the subscription request.
paymentFlow.amountTypeStringNature of redemption amount
Possible Values:
• FIXED
• VARIABLE
paymentFlow.maxAmountLongSpecifies the maximum amount that can be debited from the customer’s account for each redemption cycle.
paymentFlow.frequencyStringDefines how often the payment will be deducted from the customer’s account.
paymentFlow.subscriptionIdStringA subscriptionId generated by PhonePe to uniquely identify the user’s subscription.
paymentDetailsArrayContains the details of the payment.
Subscription Cancelled
{
    "event": "subscription.cancelled",
    "payload": {
        "merchantSubscriptionId": "MS1708797962855",
        "subscriptionId": "OMS2402242336054995042603",
        "state": "CANCELLED",
        "authWorkflowType": "TRANSACTION",
        "amountType": "FIXED",
        "maxAmount": 200,
        "frequency": "ON_DEMAND",
        "expireAt": 1737278524000,
        "pauseStartDate": 1708798426196,
        "pauseEndDate": 1708885799000
    }
}
Subscription Revoked
{
    "event": "subscription.revoked",
    "payload": {
        "merchantSubscriptionId": "MS1708797962855",
        "subscriptionId": "OMS2402242336054995042603",
        "state": "REVOKED",
        "authWorkflowType": "TRANSACTION",
        "amountType": "FIXED",
        "maxAmount": 200,
        "frequency": "ON_DEMAND",
        "expireAt": 1737278524000,
        "pauseStartDate": 1708798426196,
        "pauseEndDate": 1708885799000
    }
}
Subscription Paused
{
    "event": "subscription.paused",
    "payload": {
        "merchantSubscriptionId": "MS1708797962855",
        "subscriptionId": "OMS2402242336054995042603",
        "state": "PAUSED",
        "authWorkflowType": "TRANSACTION",
        "amountType": "FIXED",
        "maxAmount": 200,
        "frequency": "ON_DEMAND",
        "expireAt": 1737278524000,
        "pauseStartDate": 1708798426196,
        "pauseEndDate": 1708885799000
    }
}
Subscription Unpaused
{
    "event": "subscription.unpaused",
    "payload": {
        "merchantSubscriptionId": "MS1708797962855",
        "subscriptionId": "OMS2402242336054995042603",
        "state": "ACTIVE",
        "authWorkflowType": "TRANSACTION",
        "amountType": "FIXED",
        "maxAmount": 200,
        "frequency": "ON_DEMAND",
        "expireAt": 1737278524000,
        "pauseStartDate": null,
        "pauseEndDate": null
    }
}
Response Parameter
Parameter NameData TypeDescription
merchantSubscriptionIdStringUnique merchant subscription Id passed by the merchant while creating the subscription.
subscriptionIdStringThe subscription ID generated by PhonePe.
stateStringThe current status of the subscription.
Possible values:
ACTIVATION_IN_PROGRESS
ACTIVE
EXPIRED
FAILED
CANCEL_IN_PROGRESS
CANCELLED
REVOKE_IN_PROGRESS
REVOKED
PAUSE_IN_PROGRESS
PAUSED
UNPAUSE_IN_PROGRESS
authWorkflowTypeStringType of Authorization
amountTypeStringType of amount:
Fixed
Variable
maxAmountStringMaximum amount that can be charged.
frequencyStringFrequency type of subscription.
expireAtDateTimeSubscription Expiry Time in Epoch (in milliseconds)
pauseStartDateDateTimeThe start date of the subscription pause, applicable only when the subscription is in the “PAUSED” state. Otherwise, the value will be null. The date is provided in epoch (milliseconds).
pauseEndDateDateTimeThe end date of the subscription pause, applicable only when the subscription is in the “PAUSED” state. Otherwise, the value will be null. The date is provided in epoch (milliseconds).
Refund Completed
{
    "type": "PG_REFUND_COMPLETED",
    "event": "pg.refund.completed",
    "payload": {
        "merchantId": "TX12324",
        "merchantRefundId": "Refund-id-1231003121201223",
        "originalMerchantOrderId": "TX1103221212123",
        "currency": "INR",
        "amount": 100,
        "state": "COMPLETED",
        "refundId": "OMR2605201458210091350254V",
        "timestamp": 1779269301012,
        "splitInstruments": [
            {
                "instrument": {
                    "type": "ACCOUNT",
                    "maskedAccountNumber": "XXXXXXXXXXXX3627",
                    "ifsc": "IBKL0000202",
                    "accountType": "SAVINGS"
                },
                "rail": {
                    "type": "UPI",
                    "utr": "614029768413"
                },
                "currency": null,
                "amount": 100
            }
        ],
        "paymentDetails": [
            {
                "transactionId": "OMR2605201458210091350254V",
                "paymentMode": "UPI_QR",
                "timestamp": 1779269301012,
                "currency": "INR",
                "amount": 100,
                "state": "COMPLETED",
                "instrument": {
                    "type": "ACCOUNT",
                    "maskedAccountNumber": "XXXXXXXXXXXX3627",
                    "ifsc": "IBKL0000202",
                    "accountType": "SAVINGS"
                },
                "rail": {
                    "type": "UPI",
                    "utr": "614029768413"
                },
                "splitInstruments": [
                    {
                        "instrument": {
                            "type": "ACCOUNT",
                            "maskedAccountNumber": "XXXXXXXXXXXX3627",
                            "ifsc": "IBKL0000202",
                            "accountType": "SAVINGS"
                        },
                        "rail": {
                            "type": "UPI",
                            "utr": "614029768413"
                        },
                        "currency": null,
                        "amount": 100
                    }
                ],
                "phonepeTPAPTxnDetailsLink": "https://phon.pe/hvqslu39"
            }
        ],
        "phonepeTPAPTxnDetailsLink": "https://phon.pe/hvqslu39"
    }
}
Refund Failed
{
    "type": "PG_REFUND_FAILED",
    "event": "pg.refund.failed",
    "payload": {
        "merchantId": "TX13242",
        "merchantRefundId": "Refund-id-1231111303",
        "originalMerchantOrderId": "TX102123",
        "currency": "INR",
        "amount": 100,
        "state": "FAILED",
        "refundId": "OMR2605201407059813507995V",
        "timestamp": 1779266225985,
        "errorCode": "OTHERS",
        "detailedErrorCode": "500",
        "paymentDetails": [
            {
                "transactionId": "OMR2605201407059813507995V",
                "paymentMode": "UPI_QR",
                "timestamp": 1779266225985,
                "currency": "INR",
                "amount": 100,
                "state": "FAILED",
                "errorCode": "PAYMENT_ERROR",
                "detailedErrorCode": "500"
            }
        ]
    }
}

Callback Validation/Verification

For incoming requests, extract the Authorization header, validate it against the username:password you configured with us, and accept the response only if the values match.
Authorization: SHA256(username:password)

FlowCallback Type
Notificationsubscription.notification.completed
subscription.notification.failed
Redemptionsubscription.redemption.order.completed
subscription.redemption.order.failed
subscription.redemption.transaction.completed
subscription.redemption.transaction.failed

📘 Get It Right: Redemption Webhook Best Practices!


  • Always use the root-level payload.state parameter to confirm the subscription status.
  • Avoid strict deserialization of the webhook response.
  • Do not depend on the type parameter (this will be deprecated); instead, use the event parameter to identify the webhook event.
  • expireAt and timestamp values are provided in epoch time format (in milliseconds).
Subscription Notification – COMPLETED
{
  "event": "subscription.notification.completed",
  "payload": {
    "merchantId": "SWIGGY8",
    "merchantOrderId": "MO1708797962855",
    "orderId": "OMO12344",
    "amount": 100,
    "state": "NOTIFIED",
    "expireAt": 1620891733101,
    "paymentFlow": {
      "type": "SUBSCRIPTION_REDEMPTION",
      "merchantSubscriptionId": "MS121312",
      "redemptionRetryStrategy": "CUSTOM",
      "autoDebit": true,
      "validAfter": 1628229131000,
      "validUpto": 1628574731000,
      "notifiedAt": 1622539751586
    }
  }
}
Callback for Redeemed State
{
  "event": "subscription.redemption.order.completed/subscription.redemption.order.failed",
  "payload": {
   "merchantId": "SWIGGY8",
    "merchantOrderId": "MO1708797962855"
    "orderId": "OMO12344",
    "state": "COMPLETED",
    "amount": 100,
   "expireAt": 1620891733101,
    "paymentFlow": {
      "type": "SUBSCRIPTION_REDEMPTION",
      "merchantSubscriptionId": "MS121312",
      "redemptionRetryStrategy": "CUSTOM",
      "autoDebit": true,
      "validAfter": 1628229131000,
      "validUpto": 1628574731000,
      "notifiedAt": "1622539751586"
    },
    "errorCode": 
    "detailedErrorCode":   
    "paymentDetails": [
        {
            "amount": 100
            "paymentMode": "UPI_AUTO_PAY",
            "timestamp": 1620891733101      
            "transactionId": "OM124",
            "state": "COMPLETED", // FAILED, PENDING
            "rail": {
                "type": "UPI",
                "utr": "2",
                "vpa": "12****78@ybl",
                "umn": "544fcc8819d04cb08e26faa1fb07eee7@ybl"
            },
            "instrument": {
                "type": "ACCOUNT",
                "maskedAccountNumber": "******1234",
                "ifsc": "VISA",
                "accountHolderName": "Harshad",
                "accountType": "SAVINGS"
            },
            "errorCode": 
           "detailedErrorCode": 
        }
    ]
  }
}
Callback for Redemption Attempt
{
  "event": "subscription.redemption.transaction.completed/subscription.redemption.transaction.failed",
  "payload": {
   "merchantId": "SWIGGY8",
    "merchantOrderId": "MO1708797962855"
    "orderId": "OMO12344",
    "state": "PENDING",
    "amount": 100,
   "expireAt": 1620891733101,
    "paymentFlow": {
      "type": "SUBSCRIPTION_REDEMPTION",
      "merchantSubscriptionId": "MS121312",
      "redemptionRetryStrategy": "CUSTOM",
      "autoDebit": true,
      "validAfter": 1628229131000,
      "validUpto": 1628574731000,
      "notifiedAt": 1622539751586
    },
    "errorCode": 
    "detailedErrorCode":   
    "paymentDetails": [
        {
            "amount": 100
            "paymentMode": "UPI_AUTO_PAY",
            "timestamp": 1620891733101      
            "transactionId": "OM124",
            "state": "COMPLETED", // FAILED, PENDING
            "rail": {
                "type": "UPI",
                "utr": "2",
                "vpa": "12****78@ybl",
                "umn": "544fcc8819d04cb08e26faa1fb07eee7@ybl"
            },
            "instrument": {
                "type": "ACCOUNT",
                "maskedAccountNumber": "******1234",
                "ifsc": "VISA",
                "accountHolderName": "Harshad",
                "accountType": "SAVINGS"
            },
            "errorCode": 
           "detailedErrorCode": 
        }
    ]
  }
}

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.

Is this article helpful?