Payments API
Accept UPI payments through RobinPay's payments platform. Create an order, hand your customer the payment link, and get notified the moment it's paid.
Introduction
All API requests are made to the gateway over HTTPS. The base URL is:
https://api.robinpay.in
Every endpoint is versioned under /v1. Requests and
responses are JSON. Monetary amounts are always integers in
paise (₹1 = 100 paise) to avoid floating-point rounding.
{ "success": true, "data": { … } }; errors as
{ "success": false, "error": { "code", "message" } }.
Authentication
Authenticate server-to-server calls with an API key, issued from the merchant portal under API keys, sent as a bearer token:
Authorization: Bearer rb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
Or, equivalently, the X-RB-API-Key header. Keep the key
secret and server-side — never ship it in client code. Rotate or
revoke it any time from the portal.
rb_live_… keys run in live mode (real orders, your
live wallet); rb_test_… keys run in sandbox mode
(test orders, a pre-funded test wallet). The key alone decides the
mode — the request body and responses are identical. Live and sandbox
data never mix. See Sandbox / test mode.
How payments work
RobinPay is your payment provider. You integrate with RobinPay only — create an order and RobinPay provides the UPI payment rail your customer pays on. There are no third-party accounts, credentials, or banks for you to set up or manage; RobinPay runs the entire payment infrastructure for your account.
NO_PROVIDER_CONFIGURED — contact your account manager.
Payment outcomes
When a customer pays — or the payment link expires — RobinPay
reconciles the order automatically. There's nothing for you to set
up; you read the result by polling
Retrieve an order (the order's
status reflects the latest outcome).
How an outcome maps to order status
| Payment result | Order becomes | Effect |
|---|---|---|
| Paid | success | Order marked paid; commission captured |
| Link expired unpaid | expired | Commission released |
| Failed / cancelled | failed | Commission released |
Sandbox / test mode
Sandbox lets you build and test the full integration without moving real money. It's the same API and the same flow — only the API key differs — so code written against sandbox works unchanged in live.
1. Generate a sandbox API key
In the merchant portal → API keys, choose Sandbox when
issuing a key. Sandbox keys are prefixed rb_test_ (live
keys are rb_live_). Use the sandbox key as the bearer
token exactly like a live key.
2. Create test orders
Call POST /v1/orders with your sandbox key — same body
as live. Test orders carry a separate environment
(test) and draw on your auto-funded sandbox wallet
so commission checks never block testing. They're visible in the
merchant portal alongside live
— switch the Live / Sandbox toggle on the Orders page to view
them, and the dashboard shows both your live and sandbox balances.
amount drives the test result
(amount is always an integer in paise — ₹1 = 100 paise):
-
ends in
.00→ paid success — e.g.amount: 10000(₹100.00) -
ends in
.51→ failed — e.g.amount: 10051(₹100.51)
…00 succeeds, …51 fails). The outcome is
reconciled automatically, exactly like live.
rb_test_ key only ever
sees test data, and rb_live_ only live. The order
lifecycle, retrieve, and error shapes are identical in both.
Create an order
Creates a payment order and returns a paymentLink — a
RobinPay-hosted payment page for your customer to pay on. RobinPay
prepares the payment first, so a successful response always carries a
ready-to-use link.
Body parameters
| Field | Type | Description | |
|---|---|---|---|
amount | integer | required | Amount in paise. Min 100 (₹1). |
currency | string | required | INR |
method | string | required | One of upi, card, netbanking, wallet. |
deliveryType | string | required | link (hosted page). UPI intent / QR (qr) is temporarily unavailable. |
merchantOrderId | string | required | Your own order id. 1–64 chars: letters, digits, _, -. Unique per merchant. |
idempotencyKey | string | required | 8–128 chars. A retry with the same key + body never creates a duplicate — it returns the existing orderId with idempotentReplay: true and a pointer to GET /v1/orders/{orderId} (the create response is a snapshot, so fetch the order for its current status). |
expiresInMinutes | integer | optional | Minutes until the link expires. Min 5; defaults to 5 if omitted. |
description | string | optional | Free-text note, ≤ 200 chars. |
Example request
curl -sS -X POST https://api.robinpay.in/v1/orders \
-H 'Content-Type: application/json' \
-H 'Authorization: Bearer rb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx' \
-d '{
"amount": 10000,
"currency": "INR",
"method": "upi",
"deliveryType": "link",
"merchantOrderId": "order-1001",
"idempotencyKey": "order-1001-9f2a7c",
"expiresInMinutes": 10
}'
Example response
{
"success": true,
"data": {
"orderId": "ORD_01KV8AZGJ1PFY5NJ2P1PB2YH1Y",
"merchantOrderId": "order-1001",
"status": "pending",
"amount": "10000",
"currency": "INR",
"method": "upi",
"deliveryType": "link",
"paymentLink": "https://api.robinpay.in/pay/ORD_01KV8AZGJ1PFY5NJ2P1PB2YH1Y",
"expiresAt": "2026-06-16T13:57:20.000Z"
}
}
Hand paymentLink to your customer — it opens RobinPay's
hosted payment page. The order starts pending;
you'll learn the outcome by polling retrieve
once RobinPay reconciles the payment.
Retrieve an order
Fetch the current state of an order by its RobinPay
orderId (the ORD_… value from create).
status here is the live value — poll this
(rather than re-reading the create response, which is a
point-in-time snapshot) to learn the outcome.
rb_live_ key only resolves
live orders, a rb_test_ key only test orders — fetching
an order from the other mode returns
404 ORDER_NOT_FOUND.
curl -sS https://api.robinpay.in/v1/orders/ORD_01KV8AZGJ1PFY5NJ2P1PB2YH1Y \ -H 'Authorization: Bearer rb_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
{
"success": true,
"data": {
"orderId": "ORD_01KV8AZGJ1PFY5NJ2P1PB2YH1Y",
"merchantOrderId": "order-1001",
"status": "success",
"amount": "10000",
"currency": "INR",
"paidAt": "2026-06-16T13:50:11.000Z",
"expiresAt": "2026-06-16T13:57:20.000Z",
"createdAt": "2026-06-16T13:47:20.000Z"
}
}
Order lifecycle
An order moves through these states:
| Status | Meaning |
|---|---|
| pending | Created; awaiting payment. Has a live payment link. |
| success | Payment confirmed (paidAt set). Terminal. |
| failed | Payment failed. Terminal. |
| expired | The link expired before payment. Terminal. |
expiresInMinutes
(default 5). Once expired, the order moves to
expired and the link can no longer be paid.
Errors
Errors use a stable code you can branch on plus a
human-readable message:
{
"success": false,
"error": {
"code": "INSUFFICIENT_BALANCE",
"message": "Insufficient wallet balance to cover the commission on this order."
}
}
| Code | When |
|---|---|
AUTH_MISSING_TOKEN / AUTH_INVALID_TOKEN | Missing or invalid API key. |
NO_PROVIDER_CONFIGURED | Your account isn't enabled for payments yet for the requested method — contact your account manager. |
DUPLICATE_MERCHANT_ORDER_ID | merchantOrderId already used for your account. |
INSUFFICIENT_BALANCE | Prepaid wallet can't cover the order's commission. |
ALL_PROVIDERS_FAILED | RobinPay couldn't create the payment right now — safe to retry. |
ORDER_NOT_FOUND | No order with that id for your account. |
Validation errors (HTTP 400) name the offending field and the allowed format in the message — useful while integrating.