Payment Gateway API
Version 1 · sandbox only. Sandbox keys move test money; live keys don't exist yet. Get a key under Developers.
Authentication
Send your key as a bearer token. Keys are scoped (payments.read, payments.write, refunds.write) and limited to 120 requests a minute.
Authorization: Bearer <client_id>.<secret>Create a payment
Amounts are whole paisa (PKR minor units). Every create needs an Idempotency-Key (16–100 letters, digits, dashes); repeating it returns the same payment, never a second one.
curl -X POST https://<your-app>/api/public/gateway/v1/payments \
-H "Authorization: Bearer $AELAMA_KEY" \
-H "Idempotency-Key: order-1042-attempt-1" \
-H "Content-Type: application/json" \
-d '{"amount":150000,"currency":"PKR","orderRef":"1042","description":"2 notebooks"}'The response includes checkoutUrl — send your customer there. It expires after 30 minutes.
Read, cancel, refund
GET /api/public/gateway/v1/payments?from=2026-01-01&to=2026-01-31
GET /api/public/gateway/v1/payments/{id}
POST /api/public/gateway/v1/payments/{id}/cancel
POST /api/public/gateway/v1/payments/{id}/refunds {"amount":50000,"reason":"Customer request"} (+ Idempotency-Key)Statuses: requires_payment_method, pending, succeeded, failed, cancelled, expired. Treat pending as "don't charge again".
Webhooks
Events: payment.created, payment.succeeded, payment.failed, payment.cancelled, payment.expired, refund.succeeded, refund.failed, settlement.created. Each request carries:
AELAMA-Signature: t=<unix seconds>,v1=<hex HMAC-SHA256 of "<t>.<raw body>" with your signing secret>Reject a signature older than 5 minutes, compare in constant time, and store the event id — the same event may arrive more than once. Failed deliveries retry with growing waits for about a day.
import crypto from "node:crypto";
function verify(raw, header, secret) {
const { t, v1 } = Object.fromEntries(header.split(",").map((p) => p.split("=")));
if (Math.abs(Date.now() / 1000 - Number(t)) > 300) return false;
const want = crypto.createHmac("sha256", secret).update(`${t}.${raw}`).digest("hex");
return crypto.timingSafeEqual(Buffer.from(want), Buffer.from(v1));
}Errors
Errors return {"error":{"code":"...","message":"..."}} with 400 (bad input), 401 (key), 403 (scope or merchant status), 404, 409 (conflict or reused key with different data), 429 (slow down) or 503 (engine unavailable — check the payment before retrying).
Changelog: v1 (2026) — first sandbox release. Card, bank and Raast methods will be added after partner agreements.