Skip to content
upınow

Create an order

Request and response fields for creating an order, how replays work, and every error this endpoint returns.

On this page

POST /api/v1/orders creates a payment link, or replays the existing one if you send the same merchant_order_id again.

Request fields

Field Type Rules
amount number Required. More than ₹0 and at most ₹10,00,000. The amount is rounded to the nearest paisa (amount * 100, rounded), not rejected for extra decimals.
merchant_order_id string Required. Your own order id, trimmed, 1 to 120 characters. Used for idempotency: send the same value again to get the same order back.
success_url string Required. An absolute https:// or http:// URL, at most 500 characters, with no spaces and no username or password in it. The customer returns here after a successful payment.
failure_url string Required. An absolute https:// or http:// URL, at most 500 characters, with no spaces and no username or password in it. The customer returns here after an expired or failed payment.
webhook_url string Required. A public https:// or http:// URL on your own server, at most 500 characters. It must not be localhost, a single-label host, a .localhost, .internal, .local or .home.arpa name, or a private or reserved IP address. It is checked again every time UPINOW sends to it.
customer object Optional. { email, name } for your reference: email must look like an email address, name at most 120 characters. Other fields are ignored.
payment_account_id string Optional. Which of your UPI accounts this order is created on. Leave it out to use your default account. Copy the id from Settings, UPI accounts, "Account ID (for the API)" on the account you want.

A domain field is accepted for backward compatibility but has no effect; leave it out.

Response (201 Created)

json
{
  "order_id": "UPN-0123456789",
  "payable_amount": 499.07,
  "status": "pending",
  "expires_at": "2026-09-08T17:30:00.000Z",
  "payment_url": "https://app.upinow.in/pay/UPN-0123456789",
  "upi_uri": "upi://pay?pa=merchant@upi&pn=Sample%20Store&am=499.07&cu=INR&tn=UPN-0123456789",
  "qr_base64": "data:image/svg+xml;base64,...",
  "payment_account_id": "b3f8a1c4-2e6d-4a3b-9c0f-1234567890ab"
}
Field Meaning
order_id The UPINOW order id, always UPN- followed by 10 digits (older orders can start with PANME-).
payable_amount The exact amount the customer must pay; see below.
status Always pending on a fresh order.
expires_at When the link stops being payable: 5 minutes after creation.
payment_url A hosted page the customer can open directly; see Pay page and redirects.
upi_uri A upi://pay deep link you can turn into a button or a QR code yourself.
qr_base64 The same link as a ready-made QR code, an SVG data: image. Can be null if the QR could not be generated; fall back to upi_uri.
payment_account_id The UPI account this order was created on: the one you asked for, or your default account when you left the field out.

Payable amount

payable_amount is your amount plus a small offset, usually ₹0.01 to ₹0.99, so that this order's exact amount is unique and UPINOW can match the bank alert to it without any other reference. When many links share the same base amount at once, the offset can go up to ₹2.99. Always show the customer payable_amount, and ask them to pay it exactly, paise included.

Replaying an order

Calling create again with a merchant_order_id you already used returns that same order with 200 OK (not 201) and idempotent_replay: true added to the response. The new body you sent is ignored, including payment_account_id: a replay always answers with the account the order was actually created on, never a different one you might pass on the retry. status reflects the order's current state, not the state it was in when created: it can be paid or expired by the time you replay it. This makes retrying a create call after a network error safe.

Reusing a merchant_order_id whose link has already expired replays that same expired order; it does not start a new payment. Give each new payment attempt a fresh merchant_order_id.

Errors

This endpoint can answer with any of the errors on the Errors page. The ones you are most likely to see while integrating:

Status Code When
400 invalid_request The body is not JSON, a field fails validation, or the webhook_url is refused.
400 invalid_payment_account payment_account_id is malformed, unknown, removed, or belongs to a different business. One answer for every case, so nothing leaks about which ids exist.
401 missing_api_key / invalid_api_key The key is missing, unknown, or revoked.
402 payment_account_paused The account you chose (or your default account) is paused because your plan's UPI account limit was reached; see upgrade_url in the response.
403 merchant_disabled The business is disabled, including a business inside its 7-day account-deletion window.
409 upi_not_configured You have not added a UPI ID yet.
402 plan_limit_reached This month's collections reached your plan's limit.
503 all_payment_slots_busy Too many open orders share this amount right now; retry shortly.

A malformed request body, for example, comes back as 400 invalid_request.

Samples

Create an order
curl -X POST https://app.upinow.in/api/v1/orders \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 499,
    "merchant_order_id": "order_1024",
    "success_url": "https://example.com/thanks",
    "failure_url": "https://example.com/cart",
    "webhook_url": "https://example.com/upinow-webhook.php"
  }'
create-order.php (run: php create-order.php)
<?php
$apiKey = 'YOUR_API_KEY';
$body = json_encode([
    'amount' => 499,
    'merchant_order_id' => 'order_' . time(),
    'success_url' => 'https://example.com/thanks',
    'failure_url' => 'https://example.com/cart',
    'webhook_url' => 'https://example.com/upinow-webhook.php',
]);
$context = stream_context_create(['http' => [
    'method' => 'POST',
    'header' => "Content-Type: application/json\r\nX-API-Key: $apiKey\r\n",
    'content' => $body,
    'ignore_errors' => true,
]]);
$response = file_get_contents('https://app.upinow.in/api/v1/orders', false, $context);
$order = json_decode($response, true);
if (!isset($order['order_id'])) {
    exit('Error: ' . $response . PHP_EOL);
}
echo 'Order: ' . $order['order_id'] . PHP_EOL;
echo 'Pay link: ' . $order['payment_url'] . PHP_EOL;
create-order.mjs (Node 18+, run: node create-order.mjs)
const res = await fetch("https://app.upinow.in/api/v1/orders", {
  method: "POST",
  headers: { "Content-Type": "application/json", "X-API-Key": "YOUR_API_KEY" },
  body: JSON.stringify({
    amount: 499,
    merchant_order_id: "order_" + Date.now(),
    success_url: "https://example.com/thanks",
    failure_url: "https://example.com/cart",
    webhook_url: "https://example.com/upinow-webhook",
  }),
});
const order = await res.json();
if (!res.ok) throw new Error(JSON.stringify(order));
console.log("Order:", order.order_id);
console.log("Pay link:", order.payment_url);
create_order.py (run: python create_order.py)
import json, time, urllib.error, urllib.request

body = json.dumps({
    "amount": 499,
    "merchant_order_id": f"order_{int(time.time())}",
    "success_url": "https://example.com/thanks",
    "failure_url": "https://example.com/cart",
    "webhook_url": "https://example.com/upinow-webhook",
}).encode()
req = urllib.request.Request(
    "https://app.upinow.in/api/v1/orders", data=body, method="POST",
    headers={"Content-Type": "application/json", "X-API-Key": "YOUR_API_KEY"},
)
try:
    with urllib.request.urlopen(req) as res:
        order = json.load(res)
except urllib.error.HTTPError as e:
    raise SystemExit(e.read().decode())
print("Order:", order["order_id"])
print("Pay link:", order["payment_url"])