Quick start
Base URL: https://auraissue.lol/public/api/v1
- Create an API key in the admin panel (API keys).
- From your server, call
POST /order/create. - Redirect the customer to the returned
checkout_url. - Mark the order paid only after the signed
payment.successwebhook, or a status call returningPAID.
Authentication
Send your key as a Bearer token on every request (except /health):
Authorization: Bearer gw_live_xxxxxxxxxxxxxxxxxxxxxxxxgw_live_… keys create real orders. gw_test_… keys create test orders and only work while test mode is on. Live and test data are separate. All requests and responses use JSON.
Create an order
POST/api/v1/order/create
| Field | Required | Description |
|---|---|---|
merchant_order_id | Yes | Your unique order ID (max 100 chars; letters, numbers, _ - . :). |
amount | Yes | Amount in major units, up to 2 decimals (499 or 499.50). |
currency | No | Must match the gateway currency (default INR). |
customer_id, customer_name | No | Informational. |
callback_url | No | Webhook URL for this order. Falls back to the gateway default. |
return_url | No | Shown as "Return to merchant" after payment. |
Returns 201 for a new order. Sending the same merchant_order_id and amount again returns 200 with the same order (safe to retry). A different amount returns 409.
cURL
curl -X POST https://auraissue.lol/public/api/v1/order/create \
-H "Authorization: Bearer gw_live_xxx" \
-H "Content-Type: application/json" \
-d '{"merchant_order_id":"ORDER-1001","amount":499,"callback_url":"https://shop.example.com/hook","return_url":"https://shop.example.com/thanks"}'PHP
<?php
$ch = curl_init('https://auraissue.lol/public/api/v1/order/create');
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 20,
CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . getenv('GATEWAY_KEY'), 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => json_encode(['merchant_order_id' => 'ORDER-1001', 'amount' => 499]),
]);
$order = json_decode(curl_exec($ch), true);
if (!empty($order['success'])) { header('Location: ' . $order['checkout_url']); exit; }Python
import os, requests
r = requests.post("https://auraissue.lol/public/api/v1/order/create", timeout=20,
headers={"Authorization": "Bearer " + os.environ["GATEWAY_KEY"]},
json={"merchant_order_id": "ORDER-1001", "amount": 499})
order = r.json()
print(order["checkout_url"])Node.js
const r = await fetch('https://auraissue.lol/public/api/v1/order/create', {
method: 'POST',
headers: { Authorization: 'Bearer ' + process.env.GATEWAY_KEY, 'Content-Type': 'application/json' },
body: JSON.stringify({ merchant_order_id: 'ORDER-1001', amount: 499 }),
});
const order = await r.json();
console.log(order.checkout_url);Response
{
"success": true,
"order_id": "GW_AB12CD34EF56GH78",
"merchant_order_id": "ORDER-1001",
"reference": "K7M2QX9P",
"amount": 499,
"currency": "INR",
"status": "PENDING",
"upi_id": "shop@bank",
"upi_uri": "upi://pay?pa=shop@bank&pn=Shop&tn=K7M2QX9P&am=499.00&cu=INR",
"qr_url": "https://auraissue.lol/public/qr/GW_AB12CD34EF56GH78.svg",
"checkout_url": "https://auraissue.lol/public/checkout/GW_AB12CD34EF56GH78",
"expires_at": "2026-10-02T10:15:00+05:30",
"livemode": true
}Get order status
GET/api/v1/order/status/{order_id}
Add ?refresh=1 to make the gateway check the payment provider right now (rate-limited).
curl https://auraissue.lol/public/api/v1/order/status/GW_AB12CD34EF56GH78 \
-H "Authorization: Bearer gw_live_xxx"{
"success": true,
"order_id": "GW_AB12CD34EF56GH78",
"merchant_order_id": "ORDER-1001",
"amount": 499, "currency": "INR",
"status": "PAID",
"reference": "K7M2QX9P",
"transaction_id": "…",
"paid_at": "2026-10-02 10:05:11",
"expires_at": "2026-10-02T10:15:00+05:30",
"failure_reason": null,
"webhook": { "event": "payment.success", "status": "DELIVERED", "attempts": 1 },
"livemode": true
}GET/api/v1/health — no authentication; returns {"success":true,"status":"ok"}.
Webhooks
When an order changes state the gateway sends a POST with a JSON body to your callback_url. Events: payment.success, payment.failed, payment.expired.
| Header | Meaning |
|---|---|
X-Gateway-Timestamp | Unix time (seconds) when the request was signed. |
X-Gateway-Signature | Hex HMAC-SHA256 of timestamp + "." + raw body using your webhook secret. |
X-Gateway-Event | Event name. |
X-Gateway-Event-Id | Same value on every retry. Use it to ignore duplicates. |
X-Gateway-Attempt | Delivery attempt number. |
{
"event": "payment.success",
"order_id": "GW_AB12CD34EF56GH78",
"merchant_order_id": "ORDER-1001",
"amount": 499, "currency": "INR",
"reference": "K7M2QX9P",
"transaction_id": "…",
"status": "PAID",
"livemode": true,
"timestamp": "2026-10-02T10:05:11+05:30"
}Verify the signature
Always use the raw request body (not re-encoded JSON), compare in constant time, and reject timestamps older than 5 minutes. Reply with any 2xx status; anything else is retried.
PHP
<?php
$raw = file_get_contents('php://input');
$ts = $_SERVER['HTTP_X_GATEWAY_TIMESTAMP'] ?? '';
$sig = $_SERVER['HTTP_X_GATEWAY_SIGNATURE'] ?? '';
$expected = hash_hmac('sha256', $ts . '.' . $raw, getenv('WEBHOOK_SECRET'));
if (!hash_equals($expected, $sig) || abs(time() - (int)$ts) > 300) { http_response_code(400); exit; }
$event = json_decode($raw, true);
// de-duplicate on $_SERVER['HTTP_X_GATEWAY_EVENT_ID'], check amount + merchant_order_id, then fulfil
http_response_code(200); echo 'ok';Python (Flask)
import hmac, hashlib, time, os
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = os.environ["WEBHOOK_SECRET"].encode()
@app.post("/hook")
def hook():
raw = request.get_data()
ts = request.headers.get("X-Gateway-Timestamp", "")
sig = request.headers.get("X-Gateway-Signature", "")
expected = hmac.new(SECRET, ts.encode() + b"." + raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, sig) or abs(time.time() - int(ts or 0)) > 300:
abort(400)
event = request.get_json()
return "ok", 200Node.js (Express)
const crypto = require('crypto');
app.post('/hook', express.raw({ type: 'application/json' }), (req, res) => {
const ts = req.header('X-Gateway-Timestamp') || '';
const sig = req.header('X-Gateway-Signature') || '';
const expected = crypto.createHmac('sha256', process.env.WEBHOOK_SECRET).update(ts + '.' + req.body).digest('hex');
const ok = sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected));
if (!ok || Math.abs(Date.now() / 1000 - Number(ts)) > 300) return res.sendStatus(400);
const event = JSON.parse(req.body);
res.sendStatus(200);
});Retries
Failed deliveries are retried automatically (default: after 1 min, 5 min, 15 min, 1 h, 6 h; up to 6 attempts). Callback URLs must be public http(s) addresses.
Order statuses
CREATED / PENDING | Waiting for payment. |
PAID | Payment verified with the exact amount. |
FAILED | Payment could not be completed. |
EXPIRED | Not paid before the expiry time. |
CANCELLED | Cancelled. |
AMOUNT_MISMATCH | A payment was found with a different amount. It is never marked paid automatically; an admin reviews it. |
Errors
{ "success": false, "error": { "code": "invalid_amount", "message": "amount must be a positive number with at most 2 decimals." } }| HTTP | Codes |
|---|---|
| 400 | invalid_json |
| 401 | missing_api_key invalid_api_key test_mode_disabled |
| 403 | merchant_disabled test_key_required not_a_test_order |
| 404 | order_not_found not_found |
| 405 | method_not_allowed |
| 409 | duplicate_merchant_order_id order_not_pending order_already_paid |
| 413 | payload_too_large |
| 422 | invalid_amount amount_out_of_range invalid_merchant_order_id invalid_callback_url invalid_return_url invalid_currency |
| 429 | rate_limited (honour the Retry-After header) |
| 500 | server_error gateway_misconfigured |
Testing
Use a gw_test_ key (available when the gateway is in test mode). Test orders never touch the payment provider and can be driven with the simulator:
POST/api/v1/test/simulate/{order_id}
curl -X POST https://auraissue.lol/public/api/v1/test/simulate/GW_AB12CD34EF56GH78 \
-H "Authorization: Bearer gw_test_xxx" -H "Content-Type: application/json" \
-d '{"scenario":"PAID"}'Scenarios: PENDING, PAID, FAILED, WRONG_AMOUNT, EXPIRED. Outside test mode this endpoint returns 404.
Go-live checklist: switch to your gw_live_ key, test a small real payment, verify webhook signatures, and store your webhook secret server-side only.
Aura Pay · Support