Official Notice on Total Service Suspension of Khqrapi System Dear valued customers, business partners, and developers! According to the official announcement from the Bakong Team regarding the 'Amendment to the Bakong Open API Terms of Use', which restricts requests to just 100 times per day and strictly prohibits commercial use, this critical change has completely disrupted the core infrastructure of Bakong Relay as a cross-border gateway.

API KHQR
Home Features Pricing API Docs

API Reference

APIKHQR REST API · v1

Base URL 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.

Header
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

CodeMeaning
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
POST

/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)

FieldTypeDescription
amountnumberRequiredAmount (e.g. 5.00)
currencystringRequiredUSD or KHR
notestringOptionalPayment 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

JSON
{
  "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"
}
FieldTypeDescription
bill_numberstringUnique bill reference
qr_stringstringRaw KHQR EMVCo string — pass to a QR renderer library
qr_image_urlstringReady-to-use 300×300 PNG URL — use directly as <img src="...">, no extra library needed
md5stringMD5 hash — pass to /status?md5= to poll payment
amountnumberConfirmed amount
currencystringUSD or KHR
expires_atISO 8601QR expiry timestamp (5 min after generation)

Sign in to test the API live

Sign in →
GET

/api/v1/khqr/status

Poll for payment status using the md5 from generate. Returns WAITING, PAID, or EXPIRED.

Query Parameters

ParamTypeDescription
md5stringRequiredMD5 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.

Mobile apps: Never embed your API key in Flutter/mobile apps. Use the Flutter tab for the app client and the Node Backend tab for your server proxy.
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 →