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);
// ⚠️ Never embed API key in mobile app — call via your backend proxy
// If testing only, replace with: 'https://www.khqrapi.com/api/v1/khqr/generate'
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']);
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.khqrapi.com/api/v1/khqr/generate"))
.header("X-API-Key", "sk_live_...")
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(
"{\"amount\":5.00,\"currency\":\"USD\",\"note\":\"Order #1234\"}"))
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JSONObject data = new JSONObject(response.body());
String qrImageUrl = data.getString("qr_image_url");
String md5 = data.getString("md5");
payload := map[string]interface{}{
"amount": 5.00, "currency": "USD", "note": "Order #1234",
}
jsonBody, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST", "https://www.khqrapi.com/api/v1/khqr/generate", bytes.NewBuffer(jsonBody))
req.Header.Set("X-API-Key", "sk_live_...")
req.Header.Set("Content-Type", "application/json")
client := &http.Client{}
res, _ := client.Do(req)
var data map[string]interface{}
json.NewDecoder(res.Body).Decode(&data)
qrImageUrl := data["qr_image_url"].(string)
md5 := data["md5"].(string)
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "sk_live_...");
var json = JsonSerializer.Serialize(new {
amount = 5.00, currency = "USD", note = "Order #1234"
});
var res = await client.PostAsync(
"https://www.khqrapi.com/api/v1/khqr/generate",
new StringContent(json, Encoding.UTF8, "application/json"));
var data = JsonSerializer.Deserialize<JsonElement>(await res.Content.ReadAsStringAsync());
var qrImageUrl = data.GetProperty("qr_image_url").GetString();
var md5 = data.GetProperty("md5").GetString();
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'
// ⚠️ Never embed API key in mobile app — call via your backend proxy
// If testing only, replace with: 'https://www.khqrapi.com/api/v1/khqr/status?md5=$md5'
final res = await http.get(
Uri.parse('$backendUrl/payment-status?md5=$md5'),
);
final data = jsonDecode(res.body);
// data['status'] → 'WAITING' | 'PAID' | 'EXPIRED'
HttpClient client = HttpClient.newHttpClient();
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create("https://www.khqrapi.com/api/v1/khqr/status?md5=" + md5))
.header("X-API-Key", "sk_live_...")
.build();
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
JSONObject data = new JSONObject(response.body());
String status = data.getString("status"); // WAITING | PAID | EXPIRED
req, _ := http.NewRequest("GET", "https://www.khqrapi.com/api/v1/khqr/status?md5="+md5, nil)
req.Header.Set("X-API-Key", "sk_live_...")
client := &http.Client{}
res, _ := client.Do(req)
var data map[string]interface{}
json.NewDecoder(res.Body).Decode(&data)
status := data["status"].(string) // WAITING | PAID | EXPIRED
var client = new HttpClient();
client.DefaultRequestHeaders.Add("X-API-Key", "sk_live_...");
var res = await client.GetAsync($"https://www.khqrapi.com/api/v1/khqr/status?md5={md5}");
var data = JsonSerializer.Deserialize<JsonElement>(await res.Content.ReadAsStringAsync());
var status = data.GetProperty("status").GetString(); // 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 →SDK Integration Examples
Full generate + poll examples. Switch language with the tabs below.
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 <img src> directly
print("QR string:", qr["qr_string"]) # raw EMVCo string (optional)
result = wait_paid(qr["md5"])
print("Status:", result["status"]) # PAID or EXPIRED
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 <img src> directly
document.getElementById("qr").src = qr.qr_image_url;
const status = await waitPaid(qr.md5);
console.log("Status:", status); // PAID or EXPIRED
<?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']}
"; // use in <img src> directly
echo "QR string: {$qr['qr_string']}
";
$result = waitPaid($qr['md5']);
echo "Status: {$result['status']}
"; // PAID or EXPIRED
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'
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);
import java.net.http.*;
import java.net.URI;
import org.json.JSONObject;
public class KhqrClient {
static final String API_KEY = "sk_live_your_api_key_here";
static final String BASE = "https://www.khqrapi.com/api/v1";
public static JSONObject generate(double amount, String currency, String note) throws Exception {
HttpClient client = HttpClient.newHttpClient();
String body = String.format("{\"amount\":%.2f,\"currency\":\"%s\",\"note\":\"%s\"}", amount, currency, note);
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/khqr/generate"))
.header("X-API-Key", API_KEY).header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(body)).build();
HttpResponse<String> res = client.send(req, HttpResponse.BodyHandlers.ofString());
return new JSONObject(res.body());
}
public static JSONObject waitPaid(String md5, int timeoutSec) throws Exception {
HttpClient client = HttpClient.newHttpClient();
long until = System.currentTimeMillis() + timeoutSec * 1000L;
while (System.currentTimeMillis() < until) {
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create(BASE + "/khqr/status?md5=" + md5))
.header("X-API-Key", API_KEY).build();
JSONObject data = new JSONObject(client.send(req, HttpResponse.BodyHandlers.ofString()).body());
String s = data.getString("status");
if (s.equals("PAID") || s.equals("EXPIRED")) return data;
Thread.sleep(5000);
}
return null;
}
}
package main
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
)
const API_KEY = "sk_live_your_api_key_here"
const BASE = "https://www.khqrapi.com/api/v1"
func generate(amount float64, currency, note string) (map[string]interface{}, error) {
body, _ := json.Marshal(map[string]interface{}{"amount": amount, "currency": currency, "note": note})
req, _ := http.NewRequest("POST", BASE+"/khqr/generate", bytes.NewBuffer(body))
req.Header.Set("X-API-Key", API_KEY)
req.Header.Set("Content-Type", "application/json")
res, err := http.DefaultClient.Do(req)
if err != nil { return nil, err }
var data map[string]interface{}
json.NewDecoder(res.Body).Decode(&data)
return data, nil
}
func waitPaid(md5 string, timeout time.Duration) (map[string]interface{}, error) {
until := time.Now().Add(timeout)
for time.Now().Before(until) {
req, _ := http.NewRequest("GET", BASE+"/khqr/status?md5="+md5, nil)
req.Header.Set("X-API-Key", API_KEY)
res, _ := http.DefaultClient.Do(req)
var data map[string]interface{}
json.NewDecoder(res.Body).Decode(&data)
s := data["status"].(string)
if s == "PAID" || s == "EXPIRED" { return data, nil }
time.Sleep(5 * time.Second)
}
return nil, fmt.Errorf("timeout")
}
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class KhqrClient
{
const string API_KEY = "sk_live_your_api_key_here";
const string BASE = "https://www.khqrapi.com/api/v1";
static readonly HttpClient client = new HttpClient();
static async Task<JsonElement> Generate(double amount, string currency = "USD", string note = "")
{
client.DefaultRequestHeaders.Add("X-API-Key", API_KEY);
var json = JsonSerializer.Serialize(new { amount, currency, note });
var res = await client.PostAsync(BASE + "/khqr/generate",
new StringContent(json, Encoding.UTF8, "application/json"));
return JsonSerializer.Deserialize<JsonElement>(await res.Content.ReadAsStringAsync());
}
static async Task<JsonElement> WaitPaid(string md5, int timeoutSec = 300)
{
var until = DateTime.Now.AddSeconds(timeoutSec);
while (DateTime.Now < until)
{
var res = await client.GetAsync(BASE + "/khqr/status?md5=" + md5);
var data = JsonSerializer.Deserialize<JsonElement>(await res.Content.ReadAsStringAsync());
var s = data.GetProperty("status").GetString();
if (s == "PAID" || s == "EXPIRED") return data;
await Task.Delay(5000);
}
throw new TimeoutException();
}
}
Ready to integrate?
Sign up free to get your API key and start accepting KHQR payments.
Get Started Free →