SukhNexus API
Base URL: https://pay.sukhnexus.com/api/v1
Overview
The API is organised around REST. Requests and responses are JSON; amounts are always integers in the smallest currency unit (paise) — ₹499.00 is 49900. Objects follow the familiar Razorpay-style shape, so most existing integrations port with minimal changes.
UPI only: customers pay by scanning a UPI QR (or tapping "Open UPI app" on a phone). The money goes straight from the customer's bank to the merchant's verified UPI ID — SukhNexus never holds funds, charges no transaction fee, and has no settlements.
Authentication
Use HTTP Basic auth with your Key ID as username and Key Secret as password. Generate keys in Dashboard → API Keys. Test keys (snx_test_…) only see test data; live keys (snx_live_…) work after KYC approval.
curl -u snx_test_AbCd1234:YOUR_SECRET https://pay.sukhnexus.com/api/v1/orders
Keep your secret on the server. Rate limit: 300 requests/minute per key.
Integration flow
- Create an order on your server with the amount (
POST /orders). - Open checkout in the browser with
checkout.jsand the order id — or redirect the customer to the order'scheckout_url. - On success you receive
sukhnexus_payment_id,sukhnexus_order_idandsukhnexus_signature. - Verify the signature on your server, then fulfil the order.
- Optionally listen to webhooks (
payment.captured) as the source of truth.
Checkout.js
<button id="pay">Pay ₹499</button>
<script src="https://pay.sukhnexus.com/checkout.js"></script>
<script>
document.getElementById('pay').onclick = function () {
var checkout = new SukhNexus({
key: 'snx_test_AbCd1234', // key_id only
order_id: 'order_Lx8Qe2Rk91AbCd', // from your server
prefill: { name: 'Asha', email: 'asha@example.com', contact: '9876543210' },
handler: function (res) {
// POST res.sukhnexus_payment_id, res.sukhnexus_order_id,
// res.sukhnexus_signature to your server and verify
},
modal: { ondismiss: function () { console.log('closed'); } }
});
checkout.on('payment.failed', function (res) { console.log(res.error); });
checkout.open();
};
</script>
Redirect flow: pass callback_url when creating the order and send the customer to checkout_url. After payment they are redirected to callback_url?sukhnexus_payment_id=…&sukhnexus_order_id=…&sukhnexus_signature=… (or error[code], error[description] on failure).
Verify the payment signature
signature = HMAC_SHA256(order_id + "|" + payment_id, key_secret) (hex). Always verify on your server before marking an order as paid.
PHP
$expected = hash_hmac('sha256', $_POST['sukhnexus_order_id'].'|'.$_POST['sukhnexus_payment_id'], $keySecret);
if (hash_equals($expected, $_POST['sukhnexus_signature'])) {
// payment is authentic — mark order paid
}
Node.js
const crypto = require('crypto');
const expected = crypto.createHmac('sha256', keySecret)
.update(orderId + '|' + paymentId).digest('hex');
const ok = crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(signature));
Python
import hmac, hashlib
expected = hmac.new(key_secret.encode(), f"{order_id}|{payment_id}".encode(), hashlib.sha256).hexdigest()
ok = hmac.compare_digest(expected, signature)
Orders
Create an order — POST /orders
| Parameter | Type | Description |
|---|---|---|
amount * | integer | In paise, minimum 100 (₹1) |
currency | string | INR (default) |
receipt | string | Your reference, max 64 chars |
notes | object | Up to 15 key-value pairs, returned in payments & webhooks |
customer | object | name, email, contact — prefilled on checkout |
callback_url | url | Redirect target after payment (redirect flow) |
expire_in_minutes | integer | Default 1440 (24 h) |
curl -u KEY_ID:KEY_SECRET -X POST https://pay.sukhnexus.com/api/v1/orders \
-H "Content-Type: application/json" \
-d '{"amount":49900,"currency":"INR","receipt":"rcpt_11","notes":{"plan":"pro"}}'
{
"id": "order_Lx8Qe2Rk91AbCd",
"entity": "order",
"amount": 49900,
"amount_paid": 0,
"amount_due": 49900,
"currency": "INR",
"receipt": "rcpt_11",
"status": "created",
"attempts": 0,
"notes": { "plan": "pro" },
"checkout_url": "https://pay.sukhnexus.com/checkout/order_Lx8Qe2Rk91AbCd",
"expires_at": 1767312000,
"created_at": 1767225600
}
Order status: created → attempted → paid (or expired).
GET /orders | List orders. Query: count (≤100), skip, from, to (unix), receipt, status |
GET /orders/{id} | Fetch an order |
GET /orders/{id}/payments | All payment attempts for an order |
Payments
GET /payments | List payments (count, skip, from, to, status) |
GET /payments/{id} | Fetch a payment |
{
"id": "pay_Ab12Cd34Ef56Gh",
"entity": "payment",
"amount": 49900,
"currency": "INR",
"status": "captured",
"order_id": "order_Lx8Qe2Rk91AbCd",
"method": "upi",
"amount_refunded": 0,
"refund_status": null,
"captured": true,
"email": "asha@example.com",
"contact": "9876543210",
"vpa": "asha@okaxis",
"acquirer_data": { "utr": "412345678901", "rrn": "412345678901" },
"error_code": null,
"error_description": null,
"notes": { "plan": "pro" },
"created_at": 1767225650,
"captured_at": 1767225660
}
Payment status: created · pending (direct UPI awaiting merchant confirmation) · captured · failed · refunded.
Refunds
Create a refund — POST /payments/{id}/refund
amount | integer | In paise. Omit for a full refund. |
reason | string | Optional |
notes | object | Optional |
curl -u KEY_ID:KEY_SECRET -X POST https://pay.sukhnexus.com/api/v1/payments/pay_Ab12Cd34Ef56Gh/refund \
-H "Content-Type: application/json" -d '{"amount":10000,"reason":"Partial return"}'
GET /payments/{id}/refunds | Refunds of a payment |
GET /refunds | List refunds |
GET /refunds/{id} | Fetch a refund — status pending · processed · failed |
Payment Links
Create — POST /payment_links
amount | integer | In paise. Omit to let the customer enter any amount. |
min_amount | integer | Minimum when the customer chooses the amount |
title, description | string | Shown on the payment page |
reusable | boolean | Accept many payments (like a payment page) |
reference_id | string | Your invoice / reference number |
customer | object | name, email, contact |
expire_by | integer | Unix timestamp |
callback_url | url | Redirect after payment |
curl -u KEY_ID:KEY_SECRET -X POST https://pay.sukhnexus.com/api/v1/payment_links \
-H "Content-Type: application/json" \
-d '{"amount":150000,"title":"Invoice #102","customer":{"name":"Ravi","contact":"9876543210"}}'
# → { "id": "plink_…", "short_url": "https://pay.sukhnexus.com/l/ab12cd34", "status": "active", ... }
GET /payment_links | List links |
GET /payment_links/{id} | Fetch a link (status active · paid · expired · cancelled) |
POST /payment_links/{id}/cancel | Cancel an active link |
How UPI payments work
- The customer opens checkout and taps UPI. A QR with the exact amount (and an "Open UPI app" button on phones) is shown, along with the merchant's verified payee name.
- The customer pays from any UPI app; the money reaches the merchant's bank instantly.
- The customer enters the 12-digit UTR / UPI Ref. No. — the payment becomes
pendingand you receive apayment.pendingwebhook and an email. - The merchant checks the credit in their bank and clicks I received it → the payment is
captured, the orderpaid, andpayment.captured+order.paidwebhooks are sent. Not received →failed.
Fulfil orders on order.paid (or after verifying the checkout signature on a captured payment), never on pending.
Refunds are recorded through the API or dashboard (refund.processed webhook); the merchant sends the money back to the customer from their own UPI app.
Webhooks
Configure an HTTPS endpoint in Dashboard → Webhooks. We POST JSON like:
{
"entity": "event",
"event": "payment.captured",
"account_id": "mid_Q1w2E3r4T5y6U7",
"mode": "live",
"contains": ["payment"],
"payload": { "payment": { "entity": { "id": "pay_…", "amount": 49900, "status": "captured", ... } } },
"created_at": 1767225660
}
Headers: X-SukhNexus-Event, X-SukhNexus-Event-Id (use for idempotency), X-SukhNexus-Signature = HMAC_SHA256(raw_body, webhook_secret).
| Event | When |
|---|---|
payment.captured | Money received |
payment.failed | Attempt failed or cancelled |
payment.pending | Direct-UPI payment submitted with a UTR, awaiting your confirmation |
order.paid | Order fully paid |
refund.created / refund.processed | Refund lifecycle |
payment_link.paid | A payment link received a payment |
Return any 2xx within 8 seconds. Failed deliveries retry after 1 min, 5 min, 30 min, 2 h, 6 h and 24 h. You can resend any delivery from the dashboard.
Errors
HTTP 400
{ "error": { "code": "BAD_REQUEST_ERROR", "description": "The amount must be at least 100.", "field": "amount" } }
400 | Validation error — see field |
401 | Invalid key id / secret |
403 | Live mode not activated, or account suspended |
404 | Object not found (or belongs to the other mode) |
429 | Rate limit exceeded |
Test mode
With test keys, checkout shows a UPI simulator where you choose Success or Failure — no QR, no money. Test payments fire real webhooks (with "mode": "test") so you can test your whole flow end-to-end.