API Reference
APIKHQR REST API · v1
API Reference
APIKHQR REST API · v1 — Integrate KHQR payments into any app
https://www.khqrapi.com/api/v1
Authentication
Include your API key in the X-API-Key header on every request.
Get your key from the dashboard.
X-API-Key: sk_live_your_api_key_here
Security: Never expose your key in front-end or mobile code. Always call the API from your server/backend.
Error Codes
| Code | Meaning |
|---|---|
| 400 | Bad Request — invalid parameters |
| 401 | Unauthorized — missing or invalid API key |
| 404 | Not Found — transaction not found |
| 409 | Conflict — payment already claimed |
| 422 | Unprocessable — merchant not configured |
| 429 | Too Many Requests — rate limit |
| 500 | Server Error — contact support |
/api/v1/khqr/generate
Generate a KHQR payment QR code. Returns qr_string, a ready-to-use qr_image_url (PNG — use directly in <img src>), and md5 for polling.
Request Body (JSON)
| Field | Type | Description | |
|---|---|---|---|
| amount | number | Required | Amount (e.g. 5.00) |
| currency | string | Required | USD or KHR |
| note | string | Optional | Payment description (max 200) |
Example Request
curl -X POST https://www.khqrapi.com/api/v1/khqr/generate \
-H "X-API-Key: sk_live_..." -H "Content-Type: application/json" \
-d '{"amount":5.00,"currency":"USD","note":"Order #1234"}'
resp = requests.post(
"https://www.khqrapi.com/api/v1/khqr/generate",
json={"amount": 5.00, "currency": "USD", "note": "Order #1234"},
headers={"X-API-Key": "sk_live_..."},
)
data = resp.json()
print(data["qr_image_url"]) # <-- use in <img src> directly
print(data["md5"]) # <-- use to poll /status
const res = await fetch("https://www.khqrapi.com/api/v1/khqr/generate", {
method: "POST",
headers: { "X-API-Key": "sk_live_...", "Content-Type": "application/json" },
body: JSON.stringify({ amount: 5.00, currency: "USD", note: "Order #1234" }),
});
const { qr_image_url, md5 } = await res.json();
// Display QR:
document.getElementById("qr").src = qr_image_url;
$ch = curl_init("https://www.khqrapi.com/api/v1/khqr/generate");
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true,
CURLOPT_HTTPHEADER => ["X-API-Key: sk_live_...", "Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode(["amount"=>5.00,"currency"=>"USD","note"=>"Order #1234"]),
]);
$data = json_decode(curl_exec($ch), true);
// ⚠️ Call your backend proxy — never embed key in app
final res = await http.post(
Uri.parse('$backendUrl/generate-qr'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'amount': 5.00, 'currency': 'USD', 'note': 'Order #1234'}),
);
final data = jsonDecode(res.body);
// Show QR from hosted image URL (no library needed):
Image.network(data['qr_image_url']);
Response 201
{
"success": true,
"bill_number": "PAY260524123456789",
"qr_string": "000201010212...",
"qr_image_url": "https://yourdomain.com/qr/a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"md5": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"amount": 5.00,
"currency": "USD",
"expires_at": "2026-05-24T12:35:00+07:00"
}
| Field | Type | Description |
|---|---|---|
| bill_number | string | Unique bill reference |
| qr_string | string | Raw KHQR EMVCo string — pass to a QR renderer library |
| qr_image_url | string | Ready-to-use 300×300 PNG URL — use directly as <img src="...">, no extra library needed |
| md5 | string | MD5 hash — pass to /status?md5= to poll payment |
| amount | number | Confirmed amount |
| currency | string | USD or KHR |
| expires_at | ISO 8601 | QR expiry timestamp (5 min after generation) |
Sign in to test the API live
Sign in →/api/v1/khqr/status
Poll for payment status using the md5 from generate. Returns WAITING, PAID, or EXPIRED.
Query Parameters
| Param | Type | Description | |
|---|---|---|---|
| md5 | string | Required | MD5 from generate response |
curl "https://www.khqrapi.com/api/v1/khqr/status?md5=a1b2c3d4..." \
-H "X-API-Key: sk_live_..."
data = requests.get(
"https://www.khqrapi.com/api/v1/khqr/status",
params={"md5": md5},
headers={"X-API-Key": "sk_live_..."},
).json()
print(data["status"]) # WAITING | PAID | EXPIRED
const res = await fetch(`https://www.khqrapi.com/api/v1/khqr/status?md5=${md5}`, {
headers: { "X-API-Key": "sk_live_..." },
});
const { status } = await res.json(); // WAITING | PAID | EXPIRED
$data = json_decode(file_get_contents(
"https://www.khqrapi.com/api/v1/khqr/status?md5=" . urlencode($md5), false,
stream_context_create(['http' => ['header' => 'X-API-Key: sk_live_...']]),
), true);
// $data['status'] → 'WAITING' | 'PAID' | 'EXPIRED'
final res = await http.get(
Uri.parse('$backendUrl/payment-status?md5=$md5'),
);
final data = jsonDecode(res.body);
// data['status'] → 'WAITING' | 'PAID' | 'EXPIRED'
Response 200
{
"success": true,
"status": "PAID",
"bill_number": "PAY260524123456789",
"md5": "a1b2c3d4e5f6a1b2c3d4e5f6a1b2c3d4",
"amount": 5.00,
"currency": "USD",
"paid_at": "2026-05-24T12:31:42+07:00",
"expires_at": "2026-05-24T12:35:00+07:00"
}
Tip: Poll every 3–5 seconds until PAID or EXPIRED. Each md5 can only be claimed once.
Sign in to test the API live
Sign in →🐍 Python
Requires: pip install requests
import requests, time
API_KEY = "sk_live_your_api_key_here"
BASE = "https://www.khqrapi.com/api/v1"
H = {"X-API-Key": API_KEY}
def generate(amount, currency="USD", note=""):
r = requests.post(f"{BASE}/khqr/generate",
json={"amount": amount, "currency": currency, "note": note}, headers=H)
r.raise_for_status()
return r.json()
def wait_paid(md5, timeout=300):
until = time.time() + timeout
while time.time() < until:
data = requests.get(f"{BASE}/khqr/status", params={"md5": md5}, headers=H).json()
if data["status"] in ("PAID", "EXPIRED"):
return data
time.sleep(5)
# ── Usage ──────────────────────────────────────────────
qr = generate(5.00, "USD", "Order #1234")
print("QR image:", qr["qr_image_url"]) # use in
directly
print("QR string:", qr["qr_string"]) # raw EMVCo string (optional)
result = wait_paid(qr["md5"])
print("Status:", result["status"]) # PAID or EXPIRED
⚡ JavaScript / Node.js
Node.js 18+ (built-in fetch) or any backend JS runtime
const API_KEY = "sk_live_your_api_key_here";
const BASE = "https://www.khqrapi.com/api/v1";
const H = { "X-API-Key": API_KEY, "Content-Type": "application/json" };
async function generate(amount, currency = "USD", note = "") {
const res = await fetch(`${BASE}/khqr/generate`, {
method: "POST", headers: H,
body: JSON.stringify({ amount, currency, note }),
});
return res.json();
}
async function waitPaid(md5, timeout = 300_000) {
const until = Date.now() + timeout;
while (Date.now() < until) {
const { status } = await fetch(`${BASE}/khqr/status?md5=${md5}`, { headers: H }).then(r => r.json());
if (status === "PAID" || status === "EXPIRED") return status;
await new Promise(r => setTimeout(r, 5000));
}
}
// ── Usage ──────────────────────────────────────────────
const qr = await generate(5.00, "USD", "Order #1234");
console.log("QR image:", qr.qr_image_url); // use in
directly
document.getElementById("qr").src = qr.qr_image_url;
const status = await waitPaid(qr.md5);
console.log("Status:", status); // PAID or EXPIRED
🐘 PHP
PHP 8.0+ with ext-curl
<?php
const API_KEY = 'sk_live_your_api_key_here';
const BASE = 'https://www.khqrapi.com/api/v1';
function api(string $m, string $path, array $body = []): array {
$ch = curl_init(BASE . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true, CURLOPT_CUSTOMREQUEST => $m,
CURLOPT_HTTPHEADER => ['X-API-Key: ' . API_KEY, 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => $body ? json_encode($body) : null,
]);
return json_decode(curl_exec($ch), true);
}
function generate(float $amount, string $currency = 'USD', string $note = ''): array {
return api('POST', '/khqr/generate', compact('amount', 'currency', 'note'));
}
function waitPaid(string $md5, int $timeout = 300): ?array {
$until = time() + $timeout;
while (time() < $until) {
$data = api('GET', '/khqr/status?md5=' . urlencode($md5));
if (in_array($data['status'], ['PAID', 'EXPIRED'])) return $data;
sleep(5);
}
return null;
}
// ── Usage ──────────────────────────────────────────────
$qr = generate(5.00, 'USD', 'Order #1234');
echo "QR image: {$qr['qr_image_url']}\n"; // use in
directly
echo "QR string: {$qr['qr_string']}\n";
$result = waitPaid($qr['md5']);
echo "Status: {$result['status']}\n"; // PAID or EXPIRED
💙 Flutter / Dart
Add http: ^1.2.0 to pubspec.yaml
1. Flutter app calls your backend:
import 'dart:convert';
import 'package:http/http.dart' as http;
const backendUrl = 'https://your-server.com'; // your backend, NOT apikhqr directly
/// Generate QR via your backend proxy.
Future<Map<String, dynamic>> generateQR(double amount, {String currency = 'USD', String note = ''}) async {
final res = await http.post(
Uri.parse('$backendUrl/generate-qr'),
headers: {'Content-Type': 'application/json'},
body: jsonEncode({'amount': amount, 'currency': currency, 'note': note}),
);
return jsonDecode(res.body) as Map<String, dynamic>;
}
/// Poll for payment status via your backend.
Future<String> waitForPayment(String md5) async {
while (true) {
final res = await http.get(Uri.parse('$backendUrl/payment-status?md5=$md5'));
final data = jsonDecode(res.body) as Map<String, dynamic>;
if (data['status'] == 'PAID' || data['status'] == 'EXPIRED') return data['status'];
await Future.delayed(const Duration(seconds: 5));
}
}
// Usage:
// final qr = await generateQR(5.00, currency: 'USD', note: 'Order #1234');
// final qrImageUrl = qr['qr_image_url']; // ← use Image.network() directly, no library!
// final qrStr = qr['qr_string']; // raw string if you prefer qr_flutter
// final status = await waitForPayment(qr['md5']); // 'PAID' or 'EXPIRED'
2. Your backend proxy (Node.js/Express):
import express from "express";
const app = express();
app.use(express.json());
const API_KEY = process.env.APIKHQR_KEY; // store in .env, NEVER in the app
const BASE = "https://www.khqrapi.com/api/v1";
app.post("/generate-qr", async (req, res) => {
const r = await fetch(`${BASE}/khqr/generate`, {
method: "POST",
headers: { "X-API-Key": API_KEY, "Content-Type": "application/json" },
body: JSON.stringify(req.body),
});
res.json(await r.json());
});
app.get("/payment-status", async (req, res) => {
const r = await fetch(`${BASE}/khqr/status?md5=${req.query.md5}`, {
headers: { "X-API-Key": API_KEY },
});
res.json(await r.json());
});
app.listen(3000);
Ready to integrate?
Sign up free to get your API key and start accepting KHQR payments.
Get Started Free →