Overview
NRPS is a reconciliation and provisioning system for offline bank transfers. Your system creates a payment request, redirects the customer to a hosted payment page, and waits for verification. The customer pays using UPI, IMPS, or RTGS, submits the UTR/reference number, and the transaction is reviewed by authorized staff.
Once the transaction is approved or rejected, Nirmaata sends a webhook to your configured endpoint. Your system can also poll the status APIs if needed.
Payment links are single-use. After a customer submits UTR/details, the payment page is locked and cannot be opened or shared again.
- Merchant checks active payment methods.
- Merchant creates a payment request.
- Merchant redirects customer to returned payment URL.
- Customer pays and submits UTR.
- Staff verifies the transaction.
- Nirmaata sends webhook to merchant.
- Merchant marks order as paid/rejected.
Authentication
Every API request must include both the API key and API secret issued to the merchant. These credentials identify the merchant and protect the API from unauthorized order creation or status access.
X-Api-Key: YOUR_API_KEY
X-Api-Secret: YOUR_API_SECRET
cURL Example
curl -X POST "{BASE_URL}/api/v1/payments" \
-H "Content-Type: application/json" \
-H "X-Api-Key: YOUR_API_KEY" \
-H "X-Api-Secret: YOUR_API_SECRET" \
-d '{"order_id":"ORDER-1001","amount":1250.00,"customer_name":"Customer Name"}'
| X-Api-Key | Merchant public API key. |
| X-Api-Secret | Merchant private API secret. Do not expose this in frontend code. |
Fetch Active Payment Methods
Use this endpoint before creating a payment request if your checkout needs to show only currently available methods. A method is returned only when it is active and mapped to an active collection bank account.
GET /api/v1/payment-methods
{
"success": true,
"data": {
"payment_methods": [
{ "code": "UPI", "name": "UPI", "available": true },
{ "code": "IMPS", "name": "IMPS", "available": true },
{ "code": "RTGS", "name": "RTGS", "available": true }
]
}
}
Create Payment Request
This endpoint creates a hosted payment page. Store the returned payment_id and request_ref in your order table. The returned payment_url should be shown to or redirected for the customer.
If the same merchant sends the same order_id again, the existing payment request is returned instead of creating a duplicate.
If payment_expires_at is omitted, the link expires 24 hours after creation. Once UTR is submitted, the link is considered used even if staff review is still pending.
POST /api/v1/payments
Content-Type: application/json
Request Body
| order_id | Required. Unique merchant order ID. |
| amount | Required. Amount to be collected. |
| customer_id | Optional merchant customer ID. |
| customer_name | Customer name shown on payment page. |
| customer_email | Customer email shown on payment page. |
| customer_mobile | Customer mobile shown on payment page. |
| return_url | URL where customer is redirected after UTR submission. |
| failure_url | Reserved for failure/cancel flows. |
| payment_method | Optional preferred method: UPI, IMPS, or RTGS. |
| payment_expires_at | Optional expiry date/time in YYYY-MM-DD HH:MM:SS format. Defaults to 24 hours from creation. |
{
"order_id": "ORDER-1001",
"amount": 1250.00,
"customer_id": "CUST-1001",
"customer_name": "Customer Name",
"customer_email": "customer@merchantdomain.com",
"customer_mobile": "9999999999",
"return_url": "https://merchant.example.com/success",
"failure_url": "https://merchant.example.com/failed",
"payment_method": "UPI",
"payment_expires_at": "2026-06-11 18:30:00"
}
Response
{
"success": true,
"payment_id": "PAY202606100101019999",
"request_ref": "R202606100101019999",
"order_id": "ORDER-1001",
"amount": 1250,
"fee_amount": 25,
"net_amount": 1225,
"payment_expires_at": "2026-06-11 18:30:00",
"status": "Received",
"payment_origin": "checkout",
"payment_url": "{BASE_URL}/pay/{token}",
"email_sent": false
}
Create And Send Link
Use POST /api/v1/payment-links when you want NRPS to optionally send the payment link email using platform SMTP. Send send_email: true to email the customer from NRPS, or send_email: false when your own system will share the returned payment_url.
{
"order_id": "ORDER-1002",
"amount": 2500.00,
"customer_name": "Customer Name",
"customer_email": "customer@merchantdomain.com",
"payment_method": "UPI",
"payment_expires_at": "2026-06-11 18:30:00",
"send_email": true
}
{
"success": true,
"payment_id": "PAY202606100101029999",
"request_ref": "R202606100101029999",
"order_id": "ORDER-1002",
"amount": 2500,
"fee_amount": 50,
"net_amount": 2450,
"payment_expires_at": "2026-06-11 18:30:00",
"status": "Received",
"payment_origin": "payment_link",
"payment_url": "{BASE_URL}/pay/{token}",
"email_sent": true
}
Payment Origins
Every transaction carries payment_origin so merchants and operators can identify how the payment request was created.
| checkout | Payment request created from merchant checkout/API flow. |
| payment_link | Payment request created as a direct payment link. |
More origins can be added later through the normalized payment_origins table.
Customer Redirect
Redirect the customer to payment_url. The hosted page displays the amount, merchant, customer/order details, and payment instructions.
For UPI, the page displays a self-hosted QR code using the NPCI UPI URI format and UPI ID. For IMPS/RTGS, it displays account name, bank name, account number, and IFSC.
After payment, the customer submits the UTR. UTR validation is method-specific:
| UPI / IMPS | 12-16 digits only. |
| RTGS | 22-character alphanumeric UTR starting with 4-letter bank code. |
Duplicate UTR values are rejected. After successful UTR submission, the same payment page URL becomes unavailable.
Status APIs
Use status APIs to poll payment state if your system does not rely only on webhook callbacks. You can check by payment_id or request_ref.
GET /api/v1/payments/{payment_id}
GET /api/v1/payment-requests/{request_ref}
{
"success": true,
"request": {
"status": "Approved",
"request_ref": "R202606100101019999",
"payment_id": "PAY202606100101019999",
"order_id": "ORDER-1001",
"payment_method": "UPI",
"amount": "1250.00",
"fee_amount": "25.00",
"net_amount": "1225.00",
"payment_expires_at": "2026-06-11 18:30:00"
},
"payment": {
"paid_amount": "1250.00",
"utr": "123456789012",
"payment_date": "2026-06-10 01:30:00",
"verification_status": "Verified",
"verified_at": "2026-06-10 01:40:00",
"remarks": "Payment approved"
}
}
Balance Summary
This endpoint returns approved/settled collection summaries for the merchant. Use it for merchant dashboards, reconciliation, and settlement planning.
POST /api/v1/balance-summary
| start_date | Optional. Filter start datetime. |
| end_date | Optional. Filter end datetime. |
| type | consolidated or daily. |
{
"start_date": "2026-05-01 00:00:00",
"end_date": "2026-05-03 23:59:59",
"type": "daily"
}
Service Status & Support
Use the service status endpoint to check NRPS availability before initiating checkout.
GET /api/v1/service-status
{
"success": true,
"service": "Nirmaata Reconciliation and Provisioning System (NRPS)",
"status": "live",
"database": "ok",
"timestamp": "2026-06-27T10:30:00+05:30"
}
Merchants can raise support tickets through API. Failed transaction tickets require both request_id and utr.
POST /api/v1/support/tickets
{
"category": "failed_transaction",
"priority": "high",
"request_id": "R202606100101019999",
"utr": "180127144274",
"subject": "Customer debited but payment not approved",
"message": "Customer has shared bank confirmation, but the payment is still pending."
}
Supported categories are failed_transaction, refund, technical, settlement, payout, and other.
Webhook
Nirmaata sends a webhook when a submitted payment is approved, rejected, or failed by an authorized reviewer. Your system should verify the signature before updating the order.
Headers
X-Nirmaata-Webhook-Id: evt_xxx
X-Nirmaata-Webhook-Timestamp: 1780000000
X-Nirmaata-Webhook-Signature: sha256={hmac}
Payload
{
"event": "payment.approved",
"event_id": "evt_9f4b6e2d7c8a1b2c3d4e5f60",
"payment_id": "PAY202606100101019999",
"request_ref": "R202606100101019999",
"order_id": "ORDER-1001",
"amount": "1250.00",
"utr": "123456789012",
"status": "approved",
"timestamp": "2026-06-10T01:40:00+00:00"
}
Verification
- Read the raw JSON body exactly as received.
- Read
X-Nirmaata-Webhook-Timestamp. - Build the string:
timestamp + "." + raw_json_payload. - Generate HMAC SHA-256 using your webhook secret.
- Prefix with
sha256=. - Compare with
X-Nirmaata-Webhook-Signature.
sha256=<hmac_sha256(timestamp + "." + raw_json_payload, webhook_secret)>
Webhook retries reuse the same event ID, timestamp, payload, and signature. Retry actions are stored in the audit trail.
Error Codes
API failures return a stable error_code along with the HTTP status and readable message. Integrations should use error_code for program logic and show message only where suitable.
{
"success": false,
"error_code": "DUPLICATE_UTR",
"message": "This UTR has already been submitted"
}
| HTTP | Error Code | Meaning |
|---|---|---|
| 400 | BAD_REQUEST | Request body or request format is invalid. |
| 401 | INVALID_API_CREDENTIALS | API key or API secret is missing or incorrect. |
| 401 | UNAUTHORIZED | Request is not authorized. |
| 403 | IP_NOT_ALLOWED | Merchant API key is not allowed from the request IP. |
| 403 | DOMAIN_NOT_ALLOWED | Merchant API key is not allowed from the request domain. |
| 403 | FORBIDDEN | Authenticated user or API client cannot perform this action. |
| 404 | PAYMENT_NOT_FOUND | Payment page, payment request, or status record was not found. |
| 404 | NOT_FOUND | Requested resource was not found. |
| 410 | PAYMENT_EXPIRED | Payment link has expired. |
| 419 | INVALID_SECURITY_TOKEN | Security token is invalid or expired. |
| 422 | VALIDATION_FAILED | Required fields or values failed validation. |
| 422 | INVALID_AMOUNT | Amount is missing, non-numeric, zero, or negative. |
| 422 | AMOUNT_LIMIT_EXCEEDED | Amount exceeds the platform allowed limit. |
| 422 | AMOUNT_NOT_SUPPORTED | Amount is outside the configured route/provider limit. |
| 422 | METHOD_NOT_AVAILABLE | No active route is available for the requested payment method. |
| 422 | INVALID_PAYMENT_METHOD | Payment method or mapped collection account is invalid. |
| 422 | INVALID_UTR | UTR/reference format is invalid for the selected payment method. |
| 422 | DUPLICATE_UTR | UTR/reference has already been submitted or used. |
| 422 | INVALID_EXPIRY | Payment expiry value is invalid or not in the future. |
| 422 | SUPPORT_REFERENCE_REQUIRED | Failed transaction support requests require request ID and UTR. |
| 422 | SUPPORT_DETAILS_REQUIRED | Support subject or message is missing. |
| 422 | INVALID_SUPPORT_CATEGORY | Support category is not supported. |
| 500 | SERVER_ERROR | Temporary internal error. |
| 502 | UPSTREAM_ERROR | External dependency failed. |
| 503 | SERVICE_UNAVAILABLE | Service is temporarily unavailable. |
Settlements & Payouts
Settlements and payouts are managed in the NRPS portal for reconciliation. These are operational records and do not change the merchant payment API identifiers.
| Settlement Reference | Unique internal reference such as SET202606101030001234. |
| Payout Reference | Unique payout/bank reference entered by the platform team or generated by the system. |
| Mapping | One payout can clear multiple locked settlements. The portal shows which settlement was cleared in which payout. |
When payout status is paid, Nirmaata sends the merchant a payout email with settlement references covered. |
Security Notes
- Never expose API secret or webhook secret in frontend code.
- Verify every webhook signature before provisioning an order.
- Reject old webhook timestamps to reduce replay risk.
- Use merchant IP/domain whitelisting where possible.
- Treat duplicate UTR as suspicious and do not provision twice.
- All API attempts are logged, including invalid credentials and blocked IP/domain attempts.
- API errors stay JSON for integrations. Browser/payment-page errors are shown as branded HTML pages.
Status Values
| Received | Payment request has been created and customer action is pending. |
| Pending Verification | Customer submitted UTR/proof and review is pending. |
| Approved | Payment has been verified and accepted. |
| Rejected | Payment was reviewed and rejected. |
| Failed | Payment failed or was manually failed. |
| Settled | Payment has been included in a locked settlement. |