If your PhonePe integration still builds an X-VERIFY header from a salt key, it's living on borrowed time. PhonePe's own troubleshooting page now says the V1 flow is deprecated and no longer supported, and that V1 credentials can't be generated any more. A phonepe pg v2 migration isn't a version bump. Authentication, request shape, webhook verification and IDs all change. Here's what's different, based on PhonePe's current developer docs, and the order we'd do the work in.

What changed from v1 to v2

The short list:

  • Credentials. V1 used a salt key and salt index. V2 uses a client_id, client_secret and client_version, found under Developer Settings on the PhonePe Business Dashboard. PhonePe says using V1 salt keys against the V2 flow gives you 404 or Key_not_configured errors.
  • Request signing is gone, OAuth is in. V1 signed each request: SHA256 of the base64 payload plus the API path plus the salt, then ### and the salt index. V2 drops that per-request checksum. You fetch an access token and send it as a header.
  • Plain JSON bodies. No more base64-encoded request field. You post readable JSON.
  • New IDs. V2 is built around merchantOrderId for payments, and refunds have their own merchant refund ID.
  • Webhook verification is different. Instead of a checksum on the callback, PhonePe sends an Authorization header that's a SHA256 of a username and password you configure.

Each of those touches a different part of your code, which is why "just swap the endpoint" migrations fail.

The phonepe pg v2 migration, step by step

1. Get V2 credentials for UAT and production

Check the Dashboard's Developer Settings first. If they aren't there, PhonePe's guidance is to raise a ticket with their integration team, who issue credentials based on your integration type. Sandbox and production credentials are different, and so are the hosts. Put all of them in environment variables on the server. None of them belong in a frontend bundle or a mobile app.

2. Build a token cache

Every V2 API call needs an access token. You get one with a form-encoded POST:

  • Sandbox: https://api-preprod.phonepe.com/apis/pg-sandbox/v1/oauth/token
  • Production: https://api.phonepe.com/apis/identity-manager/v1/oauth/token

The body carries client_id, client_secret, client_version and grant_type=client_credentials, with Content-Type: application/x-www-form-urlencoded. The response includes access_token, issued_at, expires_at (epoch seconds) and a token_type of O-Bearer.

Then every call sends:

Authorization: O-Bearer <access_token>

Note the prefix. It's O-Bearer, not Bearer, and PhonePe calls out the space after it as a common mistake. Their troubleshooting page also says to generate a new token at least 5 minutes before expiry. So cache the token with its expires_at, refresh early, and if you run several server instances, keep it in a shared store like Redis instead of letting each instance fetch its own. On a 401, refresh once and retry once. Don't loop.

3. Rewrite payment creation

Standard Checkout payment creation goes to /checkout/v2/pay (sandbox under https://api-preprod.phonepe.com/apis/pg-sandbox, production under https://api.phonepe.com/apis/pg). The body, per the docs, has:

  • merchantOrderId: your unique order ID.
  • amount: in paise.
  • expireAfter: order expiry in seconds.
  • paymentFlow: with type set to PG_CHECKOUT, and merchantUrls.redirectUrl for where the customer lands afterwards.
  • metaInfo: optional udf1 to udf5.

Map your existing V1 transaction ID to merchantOrderId carefully. If you have orders in flight during cutover, you'll be reconciling both styles for a while, so keep a column for each.

The redirect only tells you the customer came back. It doesn't tell you they paid. Check status on the server before you mark anything as paid.

4. Status checks and webhooks

V2 has an Order Status API keyed on merchantOrderId (the docs list it under /checkout/v2/order/{merchantOrderId}/status). Call it when the customer is redirected back, and from a background job for any order still pending.

For webhooks, you configure a username and password on the Dashboard. PhonePe then sends SHA256(username:password) in the Authorization header. Compute the same hash on your side and compare it, preferably with a constant-time comparison. The events in the docs are:

  • checkout.order.completed
  • checkout.order.failed
  • pg.refund.completed
  • pg.refund.failed

Two instructions from PhonePe worth copying straight into a code comment: use the event field to identify the event and ignore type, and for payment status rely only on the root-level payload.state. And if a webhook doesn't arrive, they recommend the Order Status API as the fallback. We'd go further: treat the webhook as a hint to go check status, not as the final answer.

Also notice what this auth scheme is. The header is the same value on every webhook. It proves the sender knows your credentials, but it doesn't sign the body. That's another reason to confirm with the status API before you ship goods or grant access to paid content.

5. Refunds

Refunds move to V2 as well, with their own merchant refund ID and their own webhooks. PhonePe's troubleshooting notes say refunds must be initiated within 3 months of the successful transaction and need enough forward balance. Store the refund ID before you call the API, so retries can't create duplicates.

Gotchas we'd check before go-live

  • CORS errors mean you're calling PhonePe from the browser. Move the call to your backend. PhonePe says the same.
  • One whitelisted URL per merchant ID. The docs say multiple URL whitelisting isn't supported, and you can whitelist a domain or a subdomain, not both. Plan staging and production around that.
  • No live transactions from localhost. Test production on the registered site.
  • Old callbacks. Remove the V1 callback handler only after the last V1 order has settled or expired, not on cutover day.
  • SDKs. PhonePe publishes backend SDKs (Java, Python, .NET and others) with webhook validation helpers. Use them if your stack matches. Check the current list in their docs.

Plan the cutover behind a feature flag so you can send a small share of checkouts through V2, watch the status and webhook logs for a day, then move everything.

If you'd like a second pair of eyes on your migration, or want the V2 integration built and tested end to end, orithLabs does this kind of payment work under our web development and e-commerce services. A short first call is free, and we'll tell you what it would take before quoting anything.