Webhook 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.
  • 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:
        • checkout.order.completed: Sent when an order is successfully completed
        • checkout.order.failed: Sent when an order fails
    • 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.

ℹ️ Authorization Header!


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
{
  "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 RAVINDRA PARAMANE",
          "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
          }
        ]
      }
    ]
  }
}
Response for 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"
      }
    ]
  }
}

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

Is this article helpful?