Eko Platform Services Logo

POSTGenerate UPI QR for Cash Withdrawal

Generate a single-use UPI QR for a cash withdrawal — the customer scans it from any UPI app and pays with their own UPI PIN, then you poll the status API before handing over cash.

POST/customer/payment/upi-cash-withdrawal
Warning
The UAT environment is not available for this API — requests to https://staging.eko.in/ekoapi/v3 return an error response. Test it in Production.

Lets a retailer dispense cash to a customer who pays over UPI. You request a withdrawal for an amount; the API returns a UPI QR valid for about 60 seconds. The customer scans it from any UPI app and authorises with their own UPI PIN. Once the payment succeeds the retailer's wallet is credited (amount + commission − TDS) and the retailer hands over the cash.

It is a two-step flow:

  1. Generate QR (this API) — send the amount and location, receive a QR image and a transaction id (tid).
  2. Status — poll with that tid for the final outcome.
Important

The customer's payment settles asynchronously. A successful response here does not mean the customer has paid — tx_status is PENDING and no money has moved. Hand over cash only when the Status API returns tx_status "0".

Key facts

  • Amount: ₹100 – ₹5,000, in multiples of ₹100, whole rupees only (no paise). Validate in your app before calling.
  • Daily caps: ₹10,000 / ₹30,000 per merchant per day, set by the acquiring bank.
  • Blocked window: no transactions between 23:30 and 00:05 IST.
  • Caps and the window are NPCI / acquirer rules, not Eko's.
  • No biometric, Aadhaar or daily eKYC for the customer — they authorise with their UPI PIN.
  • Retailer prerequisite: the retailer must be onboarded and eKYC-complete for UPI Cash Withdrawal. Until then the API returns response_type_id 99016.
  • Location: send a real, current GPS fix in decimal degrees. Geo-fencing is enforced; zeros, stale values and coarse network locations are rejected.
  • Sandbox: not yet available on UAT.

Showing the QR

qr_string is a base64-encoded PNG, not a upi://pay deep link. Render it as an image:

html
<img src="data:image/png;base64,{qr_string}" alt="UPI QR" />

The customer scans it from a second device — there is no same-device app handoff. Show qr_expiry_note next to it and a countdown from qr_expiry_in. Each QR is single-use and amount-specific: never show the same QR twice and never re-initiate against the same tid.

Polling the status

The response tells you when to poll the Status API:

  1. First call after status_check_start_in seconds.
  2. Then every status_check_gap seconds.
  3. Up to status_check_attempts times.

If it is still pending after the last scheduled poll, stop, treat it as not paid, and re-query once after a few minutes.

If this call times out

If you get no response (and so no tid), the customer has not seen a QR and cannot have paid against it. Wait for the QR validity window (~60 seconds) to pass, then start a new transaction.

Why an initiation fails

A 99014 response carries the acquirer's reason in data.description. Log it and show it to the retailer — message is generic. The list below is not exhaustive; always surface the field you actually received.

ReasonWhat it means and what to do
QR not scannedThe QR expired before the customer paid. Normal. Start a new transaction.
Account is being validatedThe retailer's onboarding is still in progress at the acquirer. Retry later; it is not a request error.
Merchant's daily transaction limit exceededThe ₹10,000 / ₹30,000 per-day cap is hit. No further transactions until the next day.
Transaction restricted from 23:30 to 00:05Acquirer maintenance window. Retry after 00:05 IST.
Please complete eKYC before initiatingThe retailer's product eKYC is incomplete. A provisioning task, not a retry.
Invalid latitude/longitude · geo-fencing invalid · service restricted in this areaSend a real GPS fix as decimal degrees. Zeros, stale values and coarse network locations are rejected.
Amount exceeded limit · less than minimum · not a multiple of ₹100 · paise not allowedValidate the amount in your app before calling.
Account blocked · agent in NPCI negative registryThe retailer cannot transact. Route to your distributor; retrying will not help.
Temporarily suspended / unable to processAn upstream outage. Retry with backoff; if it persists, raise it with Eko support.
Tip
This endpoint is one step in a complete workflow:
  • UPI Cash Withdrawal — Generate a single-use UPI QR for the withdrawal amount, show it to the customer, then poll the status API on the returned schedule — hand over cash only when `tx_status` is "0".

Request

Body parameters

initiator_idstringRequired

Registered mobile number of the API user (see Platform Credentials).

example: 9962981729

user_codestringRequired

Unique code of the retailer the withdrawal is run for. The retailer must be onboarded and eKYC-complete for UPI Cash Withdrawal.

example: 34127001

amountstringRequired

Withdrawal amount in ₹, as a whole-rupee string: ₹100–₹5,000 in multiples of ₹100, no paise (e.g. `"100"`, `"2500"`). Validate before calling — an invalid amount returns `response_type_id` 99015.

example: 100

latitudestringRequired

Retailer device latitude in decimal degrees. Must be a real, current GPS fix — geo-fencing is enforced; zeros, stale values and coarse network locations are rejected.

example: 28.6139

longitudestringRequired

Retailer device longitude in decimal degrees.

example: 77.2090

customer_mobilestringoptional

Customer's 10-digit mobile number.

example: 9000000000

Response types

response_type_idMeaningNext step
99012QR generated — show `qr_string` to the customer. No money has moved yet; poll the status API with `tid`UPI Cash Withdrawal Status
99014Initiation failed — nothing to poll. Log `data.description`; start a new transaction only if the reason is retryable—
99015Invalid amount — must be ₹100–₹5,000 in multiples of ₹100, no paise. Fix the amount; do not retry as-is—
99016Not enabled for this account — a provisioning state; retrying will not help. Contact your relationship manager—

Responses

99012QR generated — show `qr_string` to the customer. No money has moved yet; poll the status API with `tid`Next:UPI Cash Withdrawal Status

  • statusnumber

    Primary success indicator (0 = success).

  • messagestring

    Human-readable response / error message.

  • response_status_idnumber

    UI display hint only (-1/0 success, 1 failure, 2 initiated, 3 refund initiated, 4 refunded). Never branch on it — use `status` (and `tx_status` for financial APIs), or `response_type_id` where documented.

  • response_type_idnumber

    A unique id for every possible response shape (success or error) — useful for client logic branching and analytics.

  • dataobject

    API-specific response payload.

    • tidstring

      Eko transaction id. Send it to the Status API and quote it in any support request.

    • qr_stringstring

      Base64-encoded PNG of the UPI QR — render it as `data:image/png;base64,<qr_string>`. Not a `upi://pay` deep link: the customer scans it from a second device. Real values are several KB (the sample is a 1×1 placeholder). Single-use and amount-specific.

    • tx_statusstring

      `PENDING` on this response: a QR has been issued, no money has moved. The payment outcome comes only from the Status API.

    • amountstring

      Withdrawal amount (₹).

    • commissionstring

      Retailer commission (₹) credited with the amount once the payment succeeds.

    • tdsstring

      TDS (₹) deducted from the commission.

    • qr_expiry_instring

      QR validity in seconds.

    • qr_expiry_notestring

      Display note to show the retailer alongside the QR.

    • qr_expiry_datestring

      Absolute QR expiry timestamp.

    • status_check_start_instring

      Seconds to wait before the first Status call.

    • status_check_gapstring

      Seconds between subsequent Status calls.

    • status_check_attemptsstring

      Maximum number of scheduled Status calls. Still pending after the last one → treat as not paid and re-query once after a few minutes.

    • fp_transaction_idstring

      Acquiring-platform reference.

    • vpastring

      The collecting VPA.

    • user_codestring

      Echo of the retailer's `user_code`.

    • timestampstring

      Response timestamp (DD-MM-YY HH:MM:SS).

    • descriptionstring

      Only on a failed initiation (`response_type_id` 99014): the failure reason from the acquiring platform. Log it and show it to the retailer — `message` is generic.

Next steps

Related