Skip to main content
Phone Pe Business Logo

Troubleshooting: Callback & Payment Status

Use this guide if a customer says they paid but you received no callback, if your payment request timed out and you don’t know whether it was created, if a transaction is stuck in a pending state, or if a refund timed out. It applies to Dynamic QR, Collect, Paylink, Integrated Static QR, Integrated EDC and Single Suite integrations.

Golden rule: The Check Payment Status API is the source of truth. Callbacks are a convenience and may not arrive. Never mark an order as failed, and never raise a new payment request for the same order, until a status check has given you a final result.

Note: For EDC devices integrated with your POS System/Application, the charge slip is the source of truth. Do not dispatch goods to customers for EDC payments without a valid charge slip.

  • Callback (push): PhonePe calls your server when a transaction reaches a final state. Callbacks go to the URL registered for your Merchant ID or Provider ID, or to the X-CALLBACK-URL sent with the request where the API supports it.
  • Status check (pull): Your server asks PhonePe for the current status. Status checks always work, even when callbacks fail.
  • Delivery: A callback counts as delivered only when your server returns an HTTP 2xx response. If it doesn’t, PhonePe retries a limited number of times, as described in Server to Server Callback.
  • No callback URL: If no callback URL is registered for your Merchant ID or Provider ID, no callback is sent. This applies to all solutions, including EDC. You must use the status APIs.

Your integration must always include the status API for your solution:

SolutionStatus API
Dynamic QR, Collect, Paylink, RefundCheck Payment Status API
Integrated Static QRTransaction List API
Integrated EDCEDC StatusCheck API
Single Suite (EDC and DQR Device)External Status API
What you seeWhat to do
Customer paid, but no callback receivedCall the status API. If it shows success, fulfil the order. Then work through the Callback Not Received: Checklist below.
Payment request (Init / Request Payment / Paylink) timed out or returned 5xxDon’t resend it, and don’t create a new request yet. Check status with the same transaction ID, then follow Payment Request Timed Out: Safe Recovery.
Status stays pendingKeep checking, following the Recommended Polling Pattern, until expiry. After expiry, make one final check. If it is still pending long after expiry, contact PhonePe.
Status check returns TRANSACTION_NOT_FOUNDThis comes back with HTTP 200 and "success": false. Check that you are in the right environment (UAT vs Production) and that the Merchant ID and transaction ID match exactly (they are case-sensitive).
Callback received for an order already marked paidExpected: callbacks can arrive more than once. Return 200 and ignore it.
Refund timed out or pendingDon’t resend the refund. Follow Refund Timed Out or Pending.

The Check Payment Status API returns HTTP 200 whatever the payment outcome. The result is in the code field.

codeMeaningFinal?What to do
PAYMENT_SUCCESSPayment completedYesFulfil the order.

Note:
For EDC devices integrated with your POS System/Application, the charge slip is the source of truth. Do not dispatch goods to customers for EDC payments without a valid charge slip.
PAYMENT_ERRORPayment failedYesMark it failed. The customer can retry with a new transaction ID.
PAYMENT_DECLINEDDeclined by the customer or bankYesMark it failed. The customer can retry with a new transaction ID.
PAYMENT_CANCELLEDRequest cancelledYesMark it cancelled.
PAYMENT_PENDINGWaiting for the customer or bankNoKeep checking.
PAYMENT_INITIATEDRequest created; payment not startedNoKeep checking.
TRANSACTION_NOT_FOUNDNo transaction with these IDs in this environment–See Quick Diagnosis.
INTERNAL_SERVER_ERROR (HTTP 500)Temporary error, or a missing or malformed X-VERIFY header–Check the X-VERIFY format (checksum###saltIndex), then retry the status check after a few seconds. Retrying a status check is always safe.
UNAUTHORIZED (HTTP 401)Checksum mismatch–Check the salt key, salt index, and the API path used in the checksum. If you send X-PROVIDER-ID, use the provider’s salt key.

Checksum for status calls (GET, no body):

Status Check Checksum (X-VERIFY)
X-VERIFY = SHA256("/v3/transaction/{merchantId}/{transactionId}/status" + saltKey) + "###" + saltIndex
  1. Wait about 5 seconds after the payment request succeeds, then make the first status check.
  2. Check every 3–5 seconds while the status is pending.
  3. Stop as soon as you get a final status.
  4. Stop regular checks when the request expires (the expiresIn you set). Then make one final check a few seconds after expiry.
  5. For transactions still not final, run a background reconciliation (for example every 15 minutes for a limited time). Don’t poll every few seconds forever.
  • Registered: Your callback URL is registered for the correct Merchant ID or Provider ID in the correct environment, or sent in X-CALLBACK-URL where supported. Registration is done by the PhonePe integration team; if you are not sure, ask them.
  • Reachable: The URL is publicly reachable over HTTPS with a valid certificate, not a localhost, private IP or test-tunnel address.
  • Not blocked: Your firewall, WAF or load balancer isn’t blocking or rate-limiting PhonePe’s requests.
  • Methods: Your endpoint accepts POST, and also PUT if that callback method was agreed for your integration.
  • Fast 2xx: Your endpoint returns HTTP 2xx quickly, within the time stated on the Server to Server Callback page. Save the callback, return 200, then process it in the background.
  • No error on valid callbacks: Your endpoint doesn’t return 4xx or 5xx for valid callbacks, for example because of a strict JSON schema or a checksum-verification bug. Ignore unknown fields.
  • Logged: Your server logs record incoming callback requests, including rejected ones.
  • Fallback: The status API is integrated as a fallback.
  • The same callback can arrive more than once. Make your handler idempotent: if the order is already in a final state, return 200 and do nothing else.
  • A callback can arrive after you have already got the result from a status check. Handle it the same way.
  • Always verify the callback’s X-VERIFY header before trusting it, as described in Server to Server Callback.
  • If a callback conflicts with your records, don’t overwrite them from the callback alone. Call the status API and use its result.
  1. Call the status API with the same transaction ID.
  2. If the status is pending or success, the request exists. Continue with it, and don’t create another.
  3. If the status is a final failure, you may create a new request with a new transaction ID.
  4. If you get TRANSACTION_NOT_FOUND, wait a few seconds and check once more. If it is still not found, the request wasn’t created, and you may create a new request with a new transaction ID.

Never reuse a transaction ID. PhonePe rejects a reused transaction ID with INVALID_TRANSACTION_ID, even if your first attempt timed out, and doesn’t return the original result. Use the status API to find out what happened.

  • A timeout, HTTP 504 or a pending response means the refund may still be in progress. It doesn’t mean the refund failed.
  • Check it with the refund status API, using the refund transaction ID you sent.
  • Don’t send the refund again while it is pending. A second refund can be rejected (for example with EXCESS_REFUND_AMOUNT).
  • Create a new refund, with a new refund transaction ID, only after the status shows that the earlier refund finally failed.
  • Don’t mark a payment as failed just because no callback arrived.
  • Don’t create a second payment request for the same order while the first one is pending or unknown.
  • Don’t retry a payment or refund with the same transaction ID.
  • Don’t rely only on the HTTP status code. Always read success and code.
  • Don’t call PhonePe APIs, or build the checksum, from the customer’s app or browser. Keep your salt key on your server only.
  • The status API shows success but you never receive callbacks, and every checklist item passes.
  • Your callback URL needs to be registered or changed.
  • A transaction is still pending long after its expiry time.
  • Status checks keep returning 5xx for several minutes, even though your X-VERIFY format is correct.

Email [email protected] with these details:

Escalation Email Template
Environment:            UAT / Production
Solution and API:       e.g. Dynamic QR – Check Payment Status
Merchant ID:
Provider ID (if any):
Transaction ID(s):      merchant transaction ID(s), max 5 examples
Time of transaction:    date, time and timezone
HTTP status and code:   e.g. 200 / PAYMENT_PENDING
errorId (if present):   from the "message" field
Callback URL (if relevant):
What you expected:
What happened:
Steps already tried:

Never send your salt key, full card numbers, PINs or unredacted logs containing them. PhonePe will never ask for your salt key.

Is this article helpful?