Build reliable payment flows with AakashPay.
AakashPay provides a client-scoped payment API, SMS-assisted verification, a customer checkout portal, device WebView configuration and server-to-server webhooks. This guide is written for production integrations and follows the currently deployed API contract.
Base URL: https://akashpay-api.axura.workers.devAPI version: v1Currencies: BDT workflowQuickstart
The standard payment lifecycle is: create an order from your backend, give the returned portalUrl to the customer, let the SMS Reader/device process the matching SMS, and poll or receive the webhook when the order becomes paid.
1. Configure
Configure your merchant account, set the payment gateway details, and keep the server-side API key in your environment.
2. Create
Use POST /v1/payment/create with amount, userId and gateway.
3. Present
Redirect the customer to the returned portal URL. The portal does not expose the API key.
4. Confirm
Poll order status or process the payment.verified webhook idempotently.
curl -X POST 'https://akashpay-api.axura.workers.dev/v1/payment/create' \\
-H 'Content-Type: application/json' \\
-H 'X-API-Key: YOUR_SERVER_SIDE_API_KEY' \\
-d '{"amount":499,"userId":"user_123","gateway":"bkash"}'Authentication
API endpoints use the X-API-Key header. The key is issued per Client ID and is hashed in server storage. Admin operations use a separate X-Admin-Token and must never be exposed to customers. Device configuration endpoints use X-Device-ID.
| Credential | Header | Where it belongs |
|---|---|---|
| Client API key | X-API-Key: … | Private PSP/bot/backend server only |
| Admin token | X-Admin-Token: … | Private operations panel only |
| Device identity | X-Device-ID: … | Trusted Android WebView device flow |
| System key | X-System-Key: … | Internal SMS/order matching service only |
Payment API
Create a 15-minute pending payment order. The gateway must be one of bkash, nagad or upay, and that gateway must have a configured number.
{
"amount": 499,
"userId": "user_123",
"gateway": "nagad"
}
// 201 Created
{
"ok": true,
"orderId": "ord_…",
"gateway": "nagad",
"number": "017XXXXXXXX",
"amount": 499,
"status": "pending",
"expiresAt": 1786818000000,
"portalUrl": "https://akashpay-api.axura.workers.dev/pay/ord_…"
}Authenticated order status for the merchant backend. The API may perform idempotent SMS matching before returning the status.
Public customer portal status. The order ID is the bearer token; do not put private user data in the URL.
Client & Android WebView connector
The Android WebView registers the device and then reads the client-scoped payment numbers. A Client ID is created from the device identity and is linked to your merchant account by the service operator. The WebView uses a five-second guarded refresh loop; it does not expose the private server API key to third-party code.
Register or refresh an Android device. The WebView should send a stable device identifier and device name. Store the returned Client ID locally.
fetch('https://akashpay-api.axura.workers.dev/v1/public/devices/register', {
method: 'POST',
headers: {'Content-Type': 'application/json'},
body: JSON.stringify({deviceId: 'android_device_hash', deviceName: 'Production Reader'})
}).then(r => r.json());Read bKash, Nagad and Upay numbers with X-Device-ID. Use this endpoint for the app's controlled settings screen.
Update device-scoped payment numbers. Validate the format in the app, then let the server remain the source of truth.
Checkout Page setup
Your customers should receive a checkout URL generated by your own backend. The browser or mobile app never needs the private API key. Your server creates the order, receives the returned portalUrl, and redirects the customer to that URL.
Step 1 · Create server route
Keep X-API-Key in an environment variable and call POST /v1/payment/create from your backend.
Step 2 · Validate input
Validate amount, user identity and gateway before sending the request. Never accept an arbitrary amount without your own business rules.
Step 3 · Redirect
Return only portalUrl and orderId to the customer-facing page, then redirect the browser to the portal URL.
Step 4 · Complete
Use the webhook or authenticated status endpoint to mark the order complete. Make fulfilment idempotent.
Minimal checkout route
// Express route: the secret stays on your server
app.post('/checkout', express.json(), async (req, res) => {
const {amount, userId, gateway = 'bkash'} = req.body;
if (!Number.isFinite(amount) || amount <= 0 || !userId) {
return res.status(400).json({error: 'invalid_checkout_input'});
}
const r = await fetch('https://akashpay-api.axura.workers.dev/v1/payment/create', {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-API-Key': process.env.AAKASHPAY_API_KEY},
body: JSON.stringify({amount, userId, gateway})
});
const data = await r.json();
if (!r.ok) return res.status(r.status).json(data);
res.json({orderId: data.orderId, checkoutUrl: data.portalUrl});
});Customer-facing button
<button id="pay">Pay securely</button>
<script>
document.querySelector('#pay').onclick = async () => {
const r = await fetch('/checkout', {method:'POST', headers:{'Content-Type':'application/json'},
body: JSON.stringify({amount: 499, userId: 'user_123', gateway: 'bkash'})});
const data = await r.json();
if (!r.ok) return alert(data.error || 'Unable to start checkout');
window.location.href = data.checkoutUrl;
};
</script>Payment redirect flow
| Stage | What happens | Customer experience |
|---|---|---|
| 1. Create | Your backend calls POST /v1/payment/create. | Customer remains on your checkout page. |
| 2. Redirect | Your backend returns portalUrl and your frontend sets window.location.href. | Customer sees the AakashPay payment instructions. |
| 3. Pending | The order waits for SMS matching and remains pending until verified or expired. | Show a “Waiting for payment” state; do not fulfil yet. |
| 4. Verified | Your webhook receives payment.verified. | Mark the order paid on your server and show the success state. |
| 5. Expired/failed | The status is no longer payable or the gateway request fails. | Show retry support without creating duplicate fulfilment. |
Checkout button with redirect and safe status page
<button id="pay">Pay securely</button>
<p id="message" role="status"></p>
<script>
const message = document.querySelector('#message');
document.querySelector('#pay').onclick = async () => {
message.textContent = 'Creating secure checkout…';
const r = await fetch('/checkout', {
method: 'POST', headers: {'Content-Type': 'application/json'},
body: JSON.stringify({amount: 499, userId: 'user_123', gateway: 'bkash'})
});
const data = await r.json();
if (!r.ok) { message.textContent = data.error || 'Unable to start checkout'; return; }
// Keep orderId for your own receipt/status page; never store the API key here.
sessionStorage.setItem('aakashpayOrderId', data.orderId);
window.location.assign(data.checkoutUrl);
};
</script>Polling a public status page
Use polling only for display. Your webhook should remain the authoritative server-side fulfilment trigger.
async function watchPayment(orderId) {
const result = document.querySelector('#payment-status');
const timer = setInterval(async () => {
const r = await fetch(`https://akashpay-api.axura.workers.dev/v1/public/payment/${encodeURIComponent(orderId)}`);
const data = await r.json();
if (!r.ok) { result.textContent = 'Unable to read payment status'; return; }
if (data.status === 'paid' || data.status === 'verified') {
clearInterval(timer); result.textContent = 'Payment confirmed';
} else if (data.status === 'expired' || data.status === 'failed') {
clearInterval(timer); result.textContent = 'Payment expired or failed';
} else {
result.textContent = 'Waiting for payment confirmation…';
}
}, 5000);
return () => clearInterval(timer);
}Webhooks
When SMS matching verifies a payment, AakashPay sends a JSON POST to the configured webhook URL. The current payload is:
{
"event": "payment.verified",
"orderId": "ord_…",
"clientId": "client_…",
"userId": "user_123",
"amount": 499,
"trxId": "TXN123456",
"balance": 1499,
"verifiedAt": 1786818000000
}Return a fast 2xx response and process the event asynchronously. Store orderId and trxId as idempotency keys. The current delivery contract does not include an HMAC signature header, so protect the endpoint with an unguessable path, allowlisted source strategy where practical, and server-side order validation. Do not trust amount or userId without checking the order in your own database.
// Node.js / Express webhook receiver
app.post('/hooks/aakashpay', express.json(), async (req, res) => {
const event = req.body;
if (event.event !== 'payment.verified' || !event.orderId || !event.trxId) {
return res.status(400).json({ok:false});
}
// 1. Load your order by event.orderId.
// 2. Ignore if already processed.
// 3. Verify amount, clientId and transaction policy.
// 4. Commit balance/fulfilment once.
await queue.add('fulfil-payment', event);
res.status(200).json({ok:true});
});cURL connector
BASE='https://akashpay-api.axura.workers.dev'
API_KEY='replace-with-server-side-key'
curl -sS "$BASE/v1/payment/ord_123" \\
-H "X-API-Key: $API_KEY"
curl -sS "$BASE/v1/public/payment/ord_123"JavaScript / Node.js
const BASE = 'https://akashpay-api.axura.workers.dev';
const API_KEY = process.env.AAKASHPAY_API_KEY;
async function createPayment(amount, userId, gateway = 'bkash') {
const response = await fetch(`${BASE}/v1/payment/create`, {
method: 'POST',
headers: {'Content-Type': 'application/json', 'X-API-Key': API_KEY},
body: JSON.stringify({amount, userId, gateway})
});
const data = await response.json();
if (!response.ok) throw new Error(data.error || 'AakashPay request failed');
return data;
}Python
import os
import requests
BASE = 'https://akashpay-api.axura.workers.dev'
API_KEY = os.environ['AAKASHPAY_API_KEY']
def create_payment(amount: float, user_id: str, gateway: str = 'bkash'):
r = requests.post(
f'{BASE}/v1/payment/create',
headers={'X-API-Key': API_KEY, 'Content-Type': 'application/json'},
json={'amount': amount, 'userId': user_id, 'gateway': gateway},
timeout=15,
)
data = r.json()
r.raise_for_status()
return data
payment = create_payment(499, 'user_123', 'nagad')
print(payment['orderId'], payment['portalUrl'])Java
HttpClient http = HttpClient.newHttpClient();
String body = "{\"amount\":499,\"userId\":\"user_123\",\"gateway\":\"bkash\"}";
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://akashpay-api.axura.workers.dev/v1/payment/create"))
.header("Content-Type", "application/json")
.header("X-API-Key", System.getenv("AAKASHPAY_API_KEY"))
.POST(HttpRequest.BodyPublishers.ofString(body))
.timeout(Duration.ofSeconds(15))
.build();
HttpResponse<String> response = http.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() < 200 || response.statusCode() >= 300) {
throw new IllegalStateException(response.body());
}
System.out.println(response.body());PHP / PSP integration
For a PSP or merchant server, keep the API key in an environment variable and call AakashPay from PHP. The PSP should return the portalUrl to its checkout controller rather than calling the public portal from a browser with the secret key.
<?php
$base = 'https://akashpay-api.axura.workers.dev';
$key = getenv('AAKASHPAY_API_KEY');
$payload = json_encode(['amount'=>499,'userId'=>'user_123','gateway'=>'bkash']);
$ch = curl_init($base.'/v1/payment/create');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $payload,
CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'X-API-Key: '.$key],
CURLOPT_RETURNTRANSFER => true,
CURLOPT_TIMEOUT => 15,
]);
$out = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_HTTP_CODE);
if ($out === false || $status < 200 || $status >= 300) {
throw new RuntimeException('AakashPay request failed: '.curl_error($ch));
}
curl_close($ch);
$result = json_decode($out, true);
header('Content-Type: application/json');
echo json_encode(['checkoutUrl'=>$result['portalUrl'], 'orderId'=>$result['orderId']]);
Errors and retry policy
| Status | Code | Meaning | Action |
|---|---|---|---|
| 400 | invalid_amount, invalid_gateway | Request validation failed. | Fix payload; do not retry unchanged. |
| 401 | missing_api_key, invalid key | Authentication failed. | Check server secret and header. |
| 403 | device_blocked | Device is blocked. | Stop automated retries and review in management. |
| 404 | order_not_found | Unknown or invalid order ID. | Check order ownership and persistence. |
| 409 | gateway_not_configured | No number is configured for gateway. | Configure the client before creating payment. |
| 5xx | server error | Transient service issue. | Exponential backoff with jitter; never duplicate fulfilment. |
Security model
Use HTTPS everywhere, keep API keys and Admin Tokens in environment variables or a secret manager, and never log full credentials. Treat the customer portal order ID as sensitive because it provides public order status. Validate webhook events against your own order record and make fulfilment idempotent. Rotate keys immediately if exposed.
Production checklist
Secrets
API key only on server; Admin Token only in the private management panel; no secrets in APK or frontend bundles.
Idempotency
Deduplicate fulfilment on orderId and trxId; webhook retries must not credit twice.
Observability
Record HTTP status, request correlation ID in your service, orderId and latency without recording secrets.
Operations
Configure gateway numbers, test a small payment, verify webhook receipt, then enable production traffic.
AakashPay Official Developer Documentation · Generated for the deployed AakashPay v2 API · Base URL https://akashpay-api.axura.workers.dev