PayRadar Dev API
รับเงินผ่าน PromptPay แล้วยืนยันยอดให้อัตโนมัติ — ไม่ต้องตรวจสลิป ไม่ต้องกระทบยอดเอง
Accept PromptPay payments and get them auto-confirmed — no slip checking, no manual reconciliation.
เรียก API เพียงครั้งเดียวเพื่อ สร้างบิลที่ล็อกยอดเงินไว้ แล้ว PayRadar จะส่งหน้าชำระเงิน แบรนด์ PayRadar กลับมาให้ (พร้อม QR payload แบบดิบ) ลูกค้าสแกนแล้วโอนได้ทันที จากนั้น มือถือ Android ที่ร้านผูกไว้จะได้ยินการแจ้งเตือนเงินเข้าจากธนาคาร PayRadar จะจับคู่กับบิล จากยอดที่ตรงเป๊ะบวกกับเศษสตางค์เฉพาะของบิลนั้น แล้วทำเครื่องหมายว่าจ่ายแล้ว พร้อม ยิง webhook ที่เซ็นลายเซ็น มาที่เซิร์ฟเวอร์ของคุณ ทำให้ยืนยันออเดอร์ได้โดยไม่ต้องเปิดดูสลิปเลย ส่วนกรณีหายากที่ listener ออฟไลน์ ยังมีช่องทางสำรองให้อัปโหลดสลิปรองรับไว้
With one API call you create a bill with a locked amount, and PayRadar returns a hosted, branded pay page (and the raw QR payload). Your customer scans and transfers. The merchant's paired Android phone hears the bank's money-in notification, PayRadar matches it to the bill by the exact amount plus a unique satang, marks it paid, and fires a signed webhook to your server. You confirm orders without ever looking at a slip — and a slip-upload fallback covers the rare case the listener is offline.
1. สร้างบิล1. Create bill
POST /v1/bill → token, QR, pay_url
2. ลูกค้าจ่าย2. Customer pays
สแกน QR PromptPay ที่ล็อกยอดไว้ แล้วโอน
Scans the locked-amount PromptPay QR & transfers
3. มือถือได้ยิน noti3. Phone hears noti
Android listener จับคู่จากยอด + เศษสตางค์
Android listener matches amount + satang
4. ยิง Webhook4. Webhook fires
POST payment.paid ที่เซ็นลายเซ็นมาหาคุณ
Signed payment.paid POST to you
Aเริ่มต้นใช้งานGetting started
ทำตาม 4 ขั้นตอนนี้ก่อนเรียก API ครั้งแรก
Four quick steps before your first API call:
- สมัครบัญชีร้านค้า ที่ cosmixon.com/api/payradar/app
- ตั้งหมายเลข PromptPay ในแดชบอร์ด นี่คือบัญชีที่จะรับเงินโอนทุกบิล
- ติดตั้งแอป Android แล้วเข้าสู่ระบบ บนมือถือที่จะวางประจำไว้ที่ร้าน การเข้าสู่ระบบจะผูกมือถือเครื่องนั้นเป็น listener ทำหน้าที่อ่านการแจ้งเตือนเงินเข้าจากธนาคารแล้วรายงานผลการจับคู่กลับมาที่ PayRadar
- คัดลอก API key จากแดชบอร์ด (จะตั้งค่า
webhook_urlในหน้านี้ด้วยเลยก็ได้)
- Sign up for a merchant account at cosmixon.com/api/payradar/app.
- Set your PromptPay number in the dashboard — this is the account that will receive every transfer.
- Install the Android app & log in on the phone that will stay at the shop. Logging in pairs that phone as your listener — it reads incoming bank notifications and reports matches back to PayRadar.
- Grab your API key from the dashboard (and optionally set your
webhook_urlthere too).
มือถือที่ใช้เป็น listener ต้องเป็น Android ที่เปิดเครื่อง เชื่อมต่ออินเทอร์เน็ต และเปิดการแจ้งเตือนของแอปธนาคารไว้ iOS อ่านการแจ้งเตือนของแอปอื่นไม่ได้ จึงใช้เป็น listener ไม่ได้ หากมือถือออฟไลน์ การยืนยันอัตโนมัติจะหยุดชั่วคราว แล้วช่องทางสำรองอัปโหลดสลิปจะทำงานแทน
The listener phone must be Android, powered on, online, and have bank-app notifications enabled. iOS can't read other apps' notifications, so it can't be a listener. If the phone goes offline, auto-confirm pauses and the slip-upload fallback takes over.
Bการยืนยันตัวตน (Authentication)Authentication
ทุก request ของ v1 ต้องยืนยันตัวตนด้วย API key ลับของคุณ โดยส่งผ่าน request header
Every v1 call is authenticated with your secret API key in a request header:
X-Api-Key: pr_live_xxxxxxxxxxxxxxxxxxxxxxxx
- API key เป็นแบบ หนึ่งคีย์ต่อหนึ่งบัญชี ให้เก็บ
pr_live_…เป็นความลับ อย่าใส่ไว้ในโค้ดฝั่ง client และอย่า commit ลง repo - API key หมุนเปลี่ยนได้ เรียก
POST /api/payradar/apikey/rotate(พร้อมBearertoken ของร้าน) เพื่อออกคีย์ใหม่และยกเลิกคีย์เดิมทันที - ค่าคีย์เดียวกันนี้ยังถูกใช้เป็น HMAC secret สำหรับตรวจลายเซ็นของ webhook (ดู Webhooks) ดังนั้นเมื่อหมุน API key ตัว webhook secret จะเปลี่ยนตามไปด้วย
- Keys are per account. Treat
pr_live_…as a secret — never expose it in client-side code or commit it to a repo. - Keys are rotatable:
POST /api/payradar/apikey/rotate(with your merchantBearertoken) issues a fresh key and invalidates the old one. - The same key value is reused as the HMAC secret for verifying webhook signatures (see Webhooks). Rotating the key rotates the webhook secret too.
หากไม่ส่ง X-Api-Key หรือส่งค่าที่ไม่ถูกต้อง จะได้รับ 401 Unauthorized และทุก request ต้องเรียกผ่าน HTTPS เท่านั้น
Missing or invalid X-Api-Key returns 401 Unauthorized. All requests must use HTTPS.
1สร้างบิลCreate a bill
สร้างคำขอชำระเงินที่ล็อกยอดไว้ พร้อมเศษสตางค์เฉพาะสำหรับใช้จับคู่ บิลมีอายุ 15 นาที
Creates a payment request with a locked amount and a unique matching satang, valid for a 15-minute window.
Request bodyRequest body
| ฟิลด์ | ชนิด | คำอธิบาย |
|---|---|---|
| Field | Type | Description |
amountจำเป็น | number > 0 | ยอดตั้งต้นเป็นบาท (เช่น 250 หรือ 250.00) ระบบจะบวกเศษสตางค์เฉพาะเข้าไปให้เองเพื่อใช้จับคู่ |
ref | string | รหัสอ้างอิงฝั่งคุณ (เช่น เลขออเดอร์ หรือเลขใบแจ้งหนี้) ระบบจะส่งค่านี้กลับมาทั้งใน status และ webhook เพื่อให้กระทบยอดได้ |
thank_you_url | string (URL) | URL ที่หน้าชำระเงินจะ redirect ลูกค้าไปหลังจ่ายสำเร็จ |
amountrequired | number > 0 | Base amount in THB (e.g. 250 or 250.00). A unique satang is added on top for matching. |
ref | string | Your own reference (order id, invoice no.). Echoed back in status & webhook so you can reconcile. |
thank_you_url | string (URL) | Where the hosted pay page redirects the customer after a confirmed payment. |
{
"amount": 250.00,
"ref": "ORDER-10231",
"thank_you_url": "https://yourshop.com/thanks"
}
ResponseResponse
{
"ok": true,
"token": "bil_7Qx2k9ZrA4",
"amount_full": 250.37,
"satang": 37,
"qr_payload": "00020101021229370016A0000006770101110213...",
"pay_url": "https://payradar.cosmixon.com/pay/bil_7Qx2k9ZrA4",
"expires_at": "2026-06-20T08:45:00Z",
"window_min": 15
}
การนำ response ไปใช้Using the response
- วิธีที่ง่ายที่สุด: redirect หรือแสดง
pay_urlให้ลูกค้า หน้านี้จะ render QR แสดงยอดที่ต้องจ่ายแบบเป๊ะ ๆ (พร้อมไฮไลต์เศษสตางค์ที่ใช้จับคู่) poll สถานะให้อัตโนมัติ และ redirect ไปthank_you_urlเมื่อจ่ายสำเร็จ ทั้งหมดอยู่ในดีไซน์แบรนด์ PayRadar - โฮสต์ QR เอง: นำ
qr_payloadไป render เป็น QR ในระบบของคุณเอง เหมาะถ้าต้องการให้ขั้นตอนชำระเงินอยู่ภายใน UI ของคุณ amount_full=amountของคุณ + เศษสตางค์เฉพาะ (.01–.99) ลูกค้าต้องโอนยอดนี้ให้ตรง เป๊ะ เพราะ listener ใช้ค่านี้แยกแยะบิลใบนี้ออกจากบิลอื่นที่มียอดบาทเท่ากัน- บิลมีอายุ 15 นาที (
window_min) เมื่อเลยexpires_atสถานะจะเปลี่ยนเป็นexpired
- Easiest: redirect or show the customer the
pay_url. It renders the QR, shows the exact amount (with the matching satang highlighted), polls status, and redirects to yourthank_you_urlon success — all branded by PayRadar. - Self-hosted QR: render
qr_payloadyourself as a QR code if you want the payment inside your own UI. amount_full= youramount+ a unique satang (.01–.99). This is the value the customer must transfer exactly; it's how the listener distinguishes this bill from any other at the same baht amount.- The bill is valid for 15 minutes (
window_min); afterexpires_atit becomesexpired.
2เช็กสถานะCheck status
Poll สถานะล่าสุดของบิล ต้องส่ง header X-Api-Key ตัวเดียวกัน ใช้ endpoint นี้เป็นแหล่งข้อมูลหลักที่เชื่อถือได้ และเป็น fallback ของ webhook
Poll a bill's current state. Requires the same X-Api-Key header. Use this as your reliable source of truth (and as a fallback to webhooks).
{
"token": "bil_7Qx2k9ZrA4",
"ref": "ORDER-10231",
"amount": 250.37,
"satang": 37,
"status": "paid", // pending | paid | expired
"paid_at": "2026-06-20T08:33:12Z",
"expires_at": "2026-06-20T08:45:00Z"
}
| status | ความหมาย |
|---|---|
| status | Meaning |
pending | รอชำระเงิน ยังอยู่ในช่วงเวลาที่กำหนด |
paid | จับคู่และยืนยันแล้ว มีค่า paid_at |
expired | เลย 15 นาทีโดยไม่พบการจับคู่ |
pending | Awaiting payment, still inside the window. |
paid | Matched and confirmed. paid_at is set. |
expired | The 15-minute window passed without a match. |
แนะนำให้ poll ทุก ๆ ไม่กี่วินาทีขณะที่สถานะยังเป็น pending แล้วหยุดเมื่อเปลี่ยนเป็น paid หรือ expired
For polling, query every few seconds while pending; stop on paid or expired.
3Webhooks
ตั้งค่า webhook_url ในแดชบอร์ดร้านค้า ทันทีที่บิลถูกจ่าย PayRadar จะส่ง HTTP POST ไปยัง URL นั้น
Set your webhook_url in the merchant dashboard. The moment a bill is paid, PayRadar sends an HTTP POST to that URL:
// Header sent by PayRadar: X-PayRadar-Signature: sha256=4f3b...e91a // hex HMAC of the raw body // Body: { "event": "payment.paid", "token": "bil_7Qx2k9ZrA4", "ref": "ORDER-10231", "amount": 250.37, "via": "auto" // "auto" (noti match) | "slip" (manual fallback) }
การตรวจสอบลายเซ็น (signature)Verifying the signature
คำนวณ HMAC-SHA256 ของ raw request body ที่ยังไม่ได้ parse โดยใช้ API key เป็น secret
เข้ารหัสผลลัพธ์เป็น hex แล้วนำไปเทียบกับค่า hex ที่อยู่หลัง sha256= ใน header
X-PayRadar-Signature และควรเทียบแบบ constant-time เพื่อป้องกัน timing leak
ข้อสำคัญคือต้องอ่าน raw body เป็น byte ก่อน parse JSON เพราะการ serialize JSON ที่ parse แล้วใหม่อาจทำให้ byte เปลี่ยน จนลายเซ็นไม่ตรง
Compute the HMAC-SHA256 of the raw, unparsed request body using your API key as the secret,
hex-encode it, and compare it to the hex after sha256= in the
X-PayRadar-Signature header. Use a constant-time comparison to avoid timing leaks.
Read the raw body bytes before any JSON parsing — re-serializing parsed JSON can change bytes and break the match.
การส่ง webhook เป็นแบบ best-effort คือมี retry แต่ไม่การันตีว่าจะถึงเสมอ จึงควรใช้ การ poll สถานะ เป็น fallback ควบคู่กันไป และออกแบบ handler ให้ idempotent เพราะ token เดิมอาจถูกส่งมาซ้ำได้มากกว่าหนึ่งครั้ง ดูตัวอย่างการตรวจสอบได้ใน ตัวอย่างโค้ด
Webhook delivery is best-effort (retried, not guaranteed). Always treat status polling as your fallback, and make your handler idempotent — the same token may arrive more than once. See the verify snippets in Code samples.
4ตัวอย่างโค้ดCode samples
ตัวอย่างการสร้างบิลและตรวจสอบ webhook ที่ส่งเข้ามา เปลี่ยน pr_live_xxx เป็น API key ของคุณ
Create a bill, then verify an incoming webhook. Replace pr_live_xxx with your key.
# Create a bill curl -X POST https://payradar.cosmixon.com/api/payradar/v1/bill \ -H "X-Api-Key: pr_live_xxx" \ -H "Content-Type: application/json" \ -d '{"amount":250.00,"ref":"ORDER-10231","thank_you_url":"https://yourshop.com/thanks"}' # Poll status curl https://payradar.cosmixon.com/api/payradar/v1/status/bil_7Qx2k9ZrA4 \ -H "X-Api-Key: pr_live_xxx"
import crypto from 'node:crypto'; import express from 'express'; const API_KEY = process.env.PAYRADAR_API_KEY; // "pr_live_xxx" const BASE = 'https://payradar.cosmixon.com/api/payradar'; // 1) Create a bill async function createBill() { const res = await fetch(`${BASE}/v1/bill`, { method: 'POST', headers: { 'X-Api-Key': API_KEY, 'Content-Type': 'application/json' }, body: JSON.stringify({ amount: 250.0, ref: 'ORDER-10231', thank_you_url: 'https://yourshop.com/thanks' }), }); return res.json(); // { ok, token, amount_full, pay_url, ... } } // 2) Verify the webhook (raw body required!) const app = express(); app.post('/payradar/webhook', express.raw({ type: '*/*' }), (req, res) => { const raw = req.body; // Buffer of the exact bytes received const got = (req.get('X-PayRadar-Signature') || '').replace('sha256=', ''); const want = crypto.createHmac('sha256', API_KEY).update(raw).digest('hex'); const a = Buffer.from(got, 'hex'), b = Buffer.from(want, 'hex'); if (a.length !== b.length || !crypto.timingSafeEqual(a, b)) { return res.status(401).send('bad signature'); } const evt = JSON.parse(raw.toString('utf8')); if (evt.event === 'payment.paid') { // idempotent: mark evt.ref / evt.token as paid } res.sendStatus(200); });
import os, hmac, hashlib, json, requests from flask import Flask, request, abort API_KEY = os.environ["PAYRADAR_API_KEY"] # "pr_live_xxx" BASE = "https://payradar.cosmixon.com/api/payradar" # 1) Create a bill def create_bill(): r = requests.post( f"{BASE}/v1/bill", headers={"X-Api-Key": API_KEY}, json={"amount": 250.00, "ref": "ORDER-10231", "thank_you_url": "https://yourshop.com/thanks"}, timeout=10, ) r.raise_for_status() return r.json() # {"ok": True, "token": ..., "pay_url": ..., ...} # 2) Verify the webhook (use the RAW body!) app = Flask(__name__) @app.post("/payradar/webhook") def webhook(): raw = request.get_data() # exact bytes, before JSON parsing got = request.headers.get("X-PayRadar-Signature", "").removeprefix("sha256=") want = hmac.new(API_KEY.encode(), raw, hashlib.sha256).hexdigest() if not hmac.compare_digest(got, want): abort(401) evt = json.loads(raw) if evt.get("event") == "payment.paid": pass # idempotent: mark evt["ref"] / evt["token"] paid return "", 200
5ข้อจำกัด & หมายเหตุLimits & notes
- อายุของบิล: 15 นาทีต่อหนึ่งบิล หลังจากนั้นสถานะจะเป็น
expiredและเงินที่โอนเข้ามาจะไม่จับคู่ให้อัตโนมัติ - เศษสตางค์ต้องไม่ซ้ำกัน: การจับคู่ใช้เศษสตางค์เฉพาะในช่วง
.01–.99ดังนั้นในยอดบาทเดียวกันจะมีบิลที่ยังไม่จ่ายพร้อมกันได้สูงสุด ประมาณ 99 บิลต่อช่วงเวลา หากมีปริมาณสูงกว่านี้ ให้ปรับยอดให้ต่างกันหรือทยอยออกบิล PayRadar ออกแบบมาสำหรับร้านขนาดเล็กถึงกลาง ไม่ได้รองรับ flash sale ที่มีลูกค้าหลักพันจ่ายราคาเดียวกันพร้อมกัน - ต้องโอนยอดให้ตรงเป๊ะ: ลูกค้าต้องโอน
amount_fullให้ตรงถึงหลักสตางค์ หากจ่ายเกินหรือขาดแม้เพียงสตางค์เดียว ระบบจะไม่ยืนยันอัตโนมัติ - listener ต้องออนไลน์: มือถือ Android ที่ผูกไว้ต้องเปิดเครื่อง ต่ออินเทอร์เน็ต และเปิดการแจ้งเตือนของแอปธนาคาร หากออฟไลน์ การยืนยันอัตโนมัติจะหยุด แล้วช่องทางสำรองอัปโหลดสลิปจะรองรับการชำระเงินแทน (webhook จะระบุ
via:"slip") - รองรับ HTTPS เท่านั้น: webhook URL ต้องเป็น
http(s)ที่เข้าถึงได้จากภายนอก ส่วน IP ภายในหรือ IP ส่วนตัวจะถูกบล็อก
- Payment window: 15 minutes per bill. After that, status becomes
expiredand a transfer won't auto-match. - Satang uniqueness: matching uses a unique satang
.01–.99, so there can be at most ~99 concurrent unpaid bills at the identical baht amount within one window. Vary your amounts or stagger bills for high volume — PayRadar is built for small-to-medium shops, not thousand-customer flash sales at one price. - Exact transfer required: the customer must transfer
amount_fullto the satang. Over- or under-paying (even by a satang) won't auto-confirm. - Listener must be online: the paired Android phone needs power, internet, and bank notifications enabled. If it's offline, auto-confirm pauses and the slip-upload fallback covers payments (
via:"slip"in the webhook). - HTTPS only. Webhook URLs must be public
http(s); private/internal IPs are blocked.
รายละเอียดวิธีที่เราจัดการข้อมูลการแจ้งเตือนอยู่ใน นโยบายความเป็นส่วนตัว PayRadar อ่านเฉพาะการแจ้งเตือนเงินเข้าจากธนาคารเท่านั้น ไม่เข้าถึงข้อมูลล็อกอินธนาคารของคุณ และไม่ขายหรือแชร์ข้อมูลของคุณให้ผู้อื่น
How we handle the notification data is documented in our Privacy Policy. PayRadar reads bank money-in notifications only, never your bank credentials, and never sells or shares your data.