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.
How Callbacks and Status Checks Work Together
- 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-URLsent 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
2xxresponse. 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:
| Solution | Status API |
|---|---|
| Dynamic QR, Collect, Paylink, Refund | Check Payment Status API |
| Integrated Static QR | Transaction List API |
| Integrated EDC | EDC StatusCheck API |
| Single Suite (EDC and DQR Device) | External Status API |
Quick Diagnosis
| What you see | What to do |
|---|---|
| Customer paid, but no callback received | Call 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 5xx | Don’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 pending | Keep 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_FOUND | This 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 paid | Expected: callbacks can arrive more than once. Return 200 and ignore it. |
| Refund timed out or pending | Don’t resend the refund. Follow Refund Timed Out or Pending. |
Reading the Check Payment Status Response
The Check Payment Status API returns HTTP 200 whatever the payment outcome. The result is in the code field.
| code | Meaning | Final? | What to do |
|---|---|---|---|
PAYMENT_SUCCESS | Payment completed | Yes | Fulfil 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_ERROR | Payment failed | Yes | Mark it failed. The customer can retry with a new transaction ID. |
PAYMENT_DECLINED | Declined by the customer or bank | Yes | Mark it failed. The customer can retry with a new transaction ID. |
PAYMENT_CANCELLED | Request cancelled | Yes | Mark it cancelled. |
PAYMENT_PENDING | Waiting for the customer or bank | No | Keep checking. |
PAYMENT_INITIATED | Request created; payment not started | No | Keep checking. |
TRANSACTION_NOT_FOUND | No 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):
X-VERIFY = SHA256("/v3/transaction/{merchantId}/{transactionId}/status" + saltKey) + "###" + saltIndexRecommended Polling Pattern
- Wait about 5 seconds after the payment request succeeds, then make the first status check.
- Check every 3–5 seconds while the status is pending.
- Stop as soon as you get a final status.
- Stop regular checks when the request expires (the
expiresInyou set). Then make one final check a few seconds after expiry. - 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.
Callback Not Received: Checklist
- Registered: Your callback URL is registered for the correct Merchant ID or Provider ID in the correct environment, or sent in
X-CALLBACK-URLwhere 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 alsoPUTif that callback method was agreed for your integration. - Fast 2xx: Your endpoint returns HTTP
2xxquickly, within the time stated on the Server to Server Callback page. Save the callback, return200, 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.
Duplicate or Late Callbacks
- The same callback can arrive more than once. Make your handler idempotent: if the order is already in a final state, return
200and 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-VERIFYheader 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.
Payment Request Timed Out: Safe Recovery
- Call the status API with the same transaction ID.
- If the status is pending or success, the request exists. Continue with it, and don’t create another.
- If the status is a final failure, you may create a new request with a new transaction ID.
- 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.
Refund Timed Out or Pending
- A timeout, HTTP
504or 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.
What Not to Do
- 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
successandcode. - Don’t call PhonePe APIs, or build the checksum, from the customer’s app or browser. Keep your salt key on your server only.
When and How to Contact PhonePe
- 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-VERIFYformat is correct.
Email [email protected] with these details:
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.