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.

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.

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.
  • Click Create to save. Your webhook is now active!

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.

Select only the specific events you need to reduce unnecessary server load and processing “Events.”

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.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
{
  "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",
          "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
          }
        ]
      }
    ]
  }
}
Subscription setup 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"
      }
    ]
  }
}
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).
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": 
        }
    ]
  }
}

Is this article helpful?