UPI payment API

Create an order. Send the customer to pay. Trust only a final status.

Your server calls eKart.money with an order id and an amount. The response has a payment link. The customer pays there. return_url only brings the browser back. An order is paid when the status API or the webhook says success.

1

Create a pay-in

POST /v1/payin with your API key, an order id, the INR amount, and a return URL.

2

Open the payment link

Send the customer to payment_url, or open upi.apps on their phone. Both are eKart.money. Neither is a provider checkout page.

3

Confirm the payment

Poll /v1/orders/status, or accept the signed webhook. Do not mark an order paid from the browser redirect.

API

Every /v1 call is POST with Content-Type: application/json and Authorization: Bearer <API key>. The examples below use this server’s address.

Create a pay-in

POST /v1/payin

The same order_id returns the stored order and does not create a second payment.

curl -s -X POST https://your-domain/v1/payin \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"ORDER_123","amount":"1000.00","return_url":"https://shop.example/thanks"}'
{
  "order_id": "ORDER_123",
  "gateway_order_no": "PI...",
  "payment_url": "https://your-domain/pay/ORDER_123",
  "status": "pending",
  "upi": {
    "upi_id": "shop@okbank",
    "uri": "upi://pay?pa=shop@okbank&am=1000.00&cu=INR&tn=ORDER_123",
    "apps": {
      "gpay": "gpay://upi/pay?pa=shop@okbank&am=1000.00&cu=INR&tn=ORDER_123",
      "phonepe": "phonepe://pay?pa=shop@okbank&am=1000.00&cu=INR&tn=ORDER_123",
      "paytm": "paytmmp://pay?pa=shop@okbank&am=1000.00&cu=INR&tn=ORDER_123",
      "bhim": "bhim://upi/pay?pa=shop@okbank&am=1000.00&cu=INR&tn=ORDER_123"
    }
  }
}
FieldRule
order_id1–64 letters, digits, _ or -. You choose it.
amountPositive INR, up to 2 decimals. 1000 and 1000.00 are both accepted.
return_urlAbsolute http or https URL. Browser return only. Not payment proof.
upiPresent while the order can still be paid. uri opens any UPI app. apps opens one app. android uses Android intents. null after a final status.

Check an order

POST /v1/orders/status

Pending and processing orders are refreshed before the response. success and failed stay final.

curl -s -X POST https://your-domain/v1/orders/status \
  -H "Authorization: Bearer $GATEWAY_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"order_id":"ORDER_123"}'
{
  "order_id": "ORDER_123",
  "type": "payin",
  "amount": "1000.00",
  "status": "success",
  "gateway_order_no": "PI...",
  "payment_url": "https://your-domain/pay/ORDER_123",
  "upi": null
}

pending processing success failed

Errors

StatusWhen
401Missing or wrong API key. Body is {"error":"Unauthorized"}.
400Body is not a JSON object, or a field fails the rules above.
404Status call for an order_id this server has never created.
409order_id was already used for a different order type.
502The UPI provider rejected the pay-in. The order is not stored, so you can retry the same id.

Webhook

When an order first becomes success or failed, eKart.money POSTs JSON to the callback URL configured for this server. The same status is not sent twice.

{
  "order_id": "ORDER_123",
  "type": "payin",
  "amount": "1000.00",
  "status": "success",
  "gateway_order_no": "PI..."
}

Header x-signature is hex HMAC-SHA256 of the raw body, keyed with your webhook secret. Verify the raw bytes before you parse JSON.

import { createHmac, timingSafeEqual } from "node:crypto";

const expected = createHmac("sha256", process.env.GATEWAY_WEBHOOK_SECRET)
  .update(rawBody)
  .digest("hex");
const ok = expected.length === signature.length
  && timingSafeEqual(Buffer.from(expected), Buffer.from(signature));

Merchant panel

Sign in with a Tron wallet through WalletConnect, on this site. eKart.money checks a signature from that address, then opens the account. A successful panel pay-in records a 1% reward against that Tron address. The reward is stored by eKart.money. It is not an on-chain token.

Go to the panel