เอกสาร API ของ TopfromPAY / TopfromPAY API Documentation

คู่มือเชื่อมต่อสำหรับ Merchant / Merchant Integration Handbook

มาตรฐานการเชื่อมต่อ (Production Ready) — contract, endpoint, webhook, และ live testing / Production-ready integration standard: contracts, endpoints, webhooks, and live testing

Contract ของ Public API คงเดิม: ทีมที่เชื่อมต่ออยู่ก่อนหน้าใช้งานต่อได้โดยไม่ต้องปรับ integration / The public API contract remains stable, so existing integrations can continue without changes.
มาตรฐานใหม่: / New standard: response ที่มี / with amount จะมี / includes copy.amount และ/หรือ / and/or copy.account_number เพื่อให้ UI ทำปุ่มคัดลอกได้ทันที / so the UI can provide copy actions immediately.

Public API Base

http://localhost:3001/api/v1

Webhook Direction

TopfromPAY → Your Callback URL (POST)

1) เมทริกซ์การยืนยันตัวตน / 1) Authentication Matrix

เลือก credential ให้ตรง endpoint / Use the correct credential for each endpoint

กติกา Runtime Key Mode / Runtime Key Mode Rule

  • ระบบบังคับ key mode ตาม / The system enforces key mode from RUNTIME_ENV อัตโนมัติ (ตรวจตอน auth) / automatically during auth.
  • ตรวจ runtime ปัจจุบันได้จาก / Check the current runtime from GET /api/v1/payout/signature-settings (runtime.mode)
  • ตรวจว่า key ที่ใช้อยู่เป็นโหมดไหนได้จาก / Check which key mode is in use from GET /api/v1/profile (authenticated_with_mode)
RuntimeDefault KeyAllowedBlockedNote
productionlivelive + legacytestเปิด test key ได้เฉพาะเมื่อ ALLOW_TEST_KEYS_IN_PRODUCTION=true
developmenttestlive + test + legacy-เหมาะกับ local integration test
CredentialHeaderUsed ForRequirement
Merchant Secret API Key (sk_live_ / sk_test_ / sk_legacy)X-API-Key/api/v1/profile, /api/v1/payment/*, /api/v1/payment/accounts, /api/v1/transactions, /api/v1/balance, /api/v1/balance/line-summary, /api/v1/webhooks, /api/v1/webhook/events, /api/v1/payout/signature-settings, /api/v1/payout/*, /api/v1/withdrawal/*จำเป็น (ใช้สร้าง payment page ได้; ควรเก็บฝั่ง backend merchant เท่านั้น)
Merchant Session (Dashboard)Cookie: connect.sid/api/payment/create-link, /api/payment/recent-linksจำเป็นเมื่อเรียก dashboard API โดยตรง
Payout SignatureIdempotency-Key, X-Signature-Timestamp, X-Signature, X-Signature-Nonce(optional)/api/v1/payout/create, /api/v1/payout/bulk, /api/v1/withdrawal/request/create, /api/v1/withdrawal/request/:requestRef/approve, /api/v1/withdrawal/request/:requestRef/rejectสร้างจาก backend merchant เอง (เปิด signature mode = ต้องมี timestamp + signature)
Webhook Signature (Callback Verify)X-Webhook-Signature, X-Webhook-Event, X-Webhook-Timestamp, X-Webhook-Delivery-Id, X-Webhook-Idempotency-Key, X-Webhook-Id(legacy), X-Webhook-VersionWebhook callback ที่ส่งจาก TopfromPAY ไปยังระบบของคุณแนะนำอย่างยิ่ง (verify ทุก callback)

2) แคตตาล็อก Endpoint / 2) Endpoint Catalog

รูปแบบนี้อ้างอิงแนว Postman docs: ระบุ method, path, auth, purpose แบบอ่านจบในหน้าเดียว (รวมทั้ง Public API และ Hosted Checkout flow) / This follows a Postman-style layout: method, path, auth, and purpose in one place, covering both the public API and hosted checkout flow.

MethodPathAuthPurpose
POST/api/v1/payment/createX-API-Key (secret)สร้างรายการรับชำระ และใช้ transaction.id ไปประกอบ hosted checkout URL ได้
POST/api/payment/create-linkSession (merchant dashboard login)สร้างลิงก์ชำระเงินแบบ hosted checkout สำหรับ dashboard/internal tooling
GET/api/payment/pay/:transactionIdPublic (no auth)ดึงข้อมูลหน้าชำระเงินจริง (QR + บัญชีธนาคาร)
GET/api/v1/profileX-API-Keyดึงข้อมูลร้านค้าและ config พื้นฐานของ key ปัจจุบัน
GET/api/v1/webhooksX-API-Keyดู callback webhook ที่ตั้งอยู่ของ merchant ปัจจุบัน
GET/api/v1/webhook/eventsX-API-Keyดึงรายการ webhook event ที่ระบบรองรับ
GET/api/v1/payment/order/:orderIdX-API-Keyเช็กสถานะรายการจาก orderId
GET/api/v1/withdrawal/requestsX-API-Keyดูคำขอถอนจากสมาชิกของร้านค้า
POST/api/v1/withdrawal/request/createX-API-Key + Signature headersสร้างคำขอถอนจากสมาชิก พร้อมเช็กยอดขั้นต่ำและ balance ร้านค้าทันที
POST/api/v1/withdrawal/request/:requestRef/approveX-API-Key + Signature headersอนุมัติคำขอถอน (ใช้ค่า `request.id` จาก endpoint create) และ reserve balance แบบ atomic ก่อนเข้า payout queue
POST/api/v1/withdrawal/request/:requestRef/rejectX-API-Key + Signature headersปฏิเสธคำขอถอนจากสมาชิก (ใช้ค่า `request.id` จาก endpoint create) ก่อนเข้า payout queue
GET/api/v1/payment/accountsX-API-Keyดึงบัญชีรับฝากที่ active ของระบบ
GET/api/v1/payout/ref/:merchantRefX-API-Keyเช็กสถานะ payout รายตัวจาก merchantRef
POST/api/v1/payout/createX-API-Key + Signature headersสร้างคำสั่งถอน 1 รายการ
POST/api/v1/payout/bulkX-API-Key + Signature headersสร้างคำสั่งถอนหลายรายการ
GET/api/payoutMerchant session (dashboard)ดูรายการถอนเงินของร้านค้า (มี copy.amount/copy.account_number)
GET/api/settlementMerchant session (dashboard)ดูรายการขอถอนเครดิต (มี copy.amount/copy.account_number)
GET/api/admin/payoutsAdmin/Payout operator sessionดูรายการ payout ทั้งระบบ (มี copy.amount/copy.account_number)
GET/api/admin/settlementsAdmin sessionดูรายการ settlement ทั้งระบบ (มี copy.amount/copy.account_number)
GET/api/admin/transactionsAdmin/Payout operator sessionดูรายการรับเงินทั้งระบบ (มี copy.amount)
GET/api/admin/bank-accountsAdmin/Payout operator sessionดูบัญชีธนาคารระบบกลาง (มี copy.account_number)
GET/api/v1/transactionsX-API-Keyดึงรายการธุรกรรมย้อนหลังแบบ paginated
GET/api/v1/balanceX-API-Keyเช็กยอดคงเหลือปัจจุบันของ merchant
GET/api/v1/balance/line-summaryX-API-Keyเช็กยอดรวมทั้งสายงาน (self + downline) ของ merchant ปัจจุบัน
GET/api/v1/payout/signature-settingsX-API-Keyดูกติกา signature ปัจจุบันจากระบบจริง
POST<your-webhook-url>X-Webhook-Signature (+ webhook headers)รับสถานะฝาก/ถอนอัตโนมัติจาก TopfromPAY (callback)

3) สัญญา Create Payment / 3) Create Payment Contract

FieldTypeRequiredDescriptionExample
amountnumberRequiredยอดที่ต้องชำระ (> 0) รองรับทศนิยม 2 ตำแหน่ง100.37
orderIdstringRequiredเลขอ้างอิงฝั่งร้านค้า (ไม่ซ้ำต่อ merchant) - production ต้องส่งทุกครั้งORD-10001
descriptionstringOptionalคำอธิบายรายการTopup
metadata.senderAccountNumberstringRequiredเลขบัญชีผู้โอนแบบเต็ม ต้องมีอย่างน้อย 8 หลัก1673585481
metadata.senderAccountLast4stringOptionalเลขท้ายบัญชีผู้โอน 4 หลัก (ถ้าไม่ส่ง ระบบจะเติมให้อัตโนมัติจาก senderAccountNumber)5481
metadata.senderBankCodestringRequiredรหัสธนาคารผู้โอน เช่น SCB, KBANK, KTB, GSBGSB
metadata.memberNamestringRequiredชื่อสมาชิก/เจ้าของบัญชีผู้โอนนาย ทองดี ใจงาม
metadata.memberNameEnstringRecommendedชื่อสมาชิกภาษาอังกฤษ (ช่วยเพิ่มความแม่นยำกรณีชื่อไทยถูกตัด)THONGDEE JAINGAM
metadata.customerIdstringOptionalรหัสสมาชิกฝั่ง merchant เพื่อ traceu_123
qrTemplatePayloadstringOptionalEMV/Thai QR payload ต้นฉบับจาก QR ร้านค้า (เช่น ถุงเงิน) ระบบจะ rebuild เป็น QR ใหม่พร้อม amount ของรายการ000201010211...
qrTemplateImageBase64stringOptionalรูป QR ต้นฉบับแบบ base64 หรือ data URL; backend จะ decode payload จากรูปแล้วสร้าง QR ใหม่พร้อม amountdata:image/png;base64,iVBORw0KGgoAAA...

[อัปเดตการเชื่อมต่อ] Create Payment Contract (มีผลทันที) / [Integration Update] Create Payment Contract (Effective Now)

ต้องส่งฟิลด์ต่อไปนี้ทุกครั้งเมื่อเรียก / Send the following fields on every call to POST /api/v1/payment/create

  1. orderId
  2. metadata.senderAccountNumber (อย่างน้อย 8 หลัก) / (at least 8 digits)
  3. metadata.senderBankCode
  4. metadata.memberName

ถ้าส่งไม่ครบ ระบบจะตอบกลับ / If any are missing, the API returns 400 PAYMENT_CONTRACT_POLICY_VIOLATION

คำแนะนำและหมายเหตุ / Recommended & Notes

  • amount รองรับทศนิยม 2 ตำแหน่ง (เช่น / supports 2 decimal places (for example 100.37)
  • แนะนำให้ส่ง / Recommended: send metadata.memberNameEn
  • senderAccountNumber ต้องมีอย่างน้อย 8 หลัก / must contain at least 8 digits
  • ถ้าไม่ส่ง / If you omit senderAccountLast4 ระบบจะเติมให้อัตโนมัติ / the system derives it automatically
  • ทำ / Keep orderId ให้ unique ต่อ merchant เสมอ / unique per merchant

โหมดไม่มี QR (ใช้งานได้) / No QR Mode (Supported)

  • merchant ที่ไม่ใช้ QR สามารถฝากผ่านบัญชีธนาคารได้ โดยดูจาก / Merchants without QR can still receive deposits through bank accounts from paymentAccounts.bankAccounts
  • ตรวจจาก response เสมอ: ถ้า / Always check the response: if transaction.qrCode เป็น / is null ให้แสดงช่องทางโอนบัญชีแทน / show bank-transfer instructions instead
  • ตรวจ / Check paymentAccounts.channels.promptPayEnabled และ / and bankTransferEnabled ก่อนแสดงปุ่มชำระเงิน / before rendering payment actions
  • สถานะช่องทางจริงเป็นแบบผสม: global switch ของระบบ + ค่า override ราย merchant / Channel availability is combined from the system-wide switch and per-merchant overrides.

ใช้ QR ต้นฉบับของร้านค้า / Using a Merchant QR Template

  • ถ้ามี QR จากแอพถุงเงินหรือ Thai QR แบบ static อยู่แล้ว สามารถส่ง / If you already have a static Thai QR from TungNgern or another merchant app, you can send qrTemplatePayload หรือ / or qrTemplateImageBase64
  • backend จะ decode payload ต้นฉบับ แล้ว rebuild เป็น QR ใหม่พร้อมจำนวนเงินของ transaction นี้ / The backend decodes the original payload and rebuilds a new QR with this transaction amount.
  • แนะนำให้ส่ง payload โดยตรงถ้ามี; เร็วกว่าและเล็กกว่าการส่งรูป base64 / Prefer sending the payload directly when available; it is smaller and more efficient than base64 image upload.
POST /api/v1/payment/create
curl -X POST 'http://localhost:3001/api/v1/payment/create' \
-H 'X-API-Key: sk_test_xxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"amount": 100.37,
"orderId": "ORD-10001",
"description": "Topup",
"metadata": {
"customerId": "u_123",
"senderAccountNumber": "1673585481",
"senderBankCode": "GSB",
"memberName": "นาย ทองดี ใจงาม",
"memberNameEn": "THONGDEE JAINGAM"
}
}'
Create Payment Success Response (201)
{
"success": true,
"transaction": {
"id": "uuid",
"orderId": "ORD-10001",
"amount": 100.37,
"fee": 2.01,
"netAmount": 98.36,
"status": "PENDING",
"paymentMethod": "PROMPTPAY",
"qrCode": "data:image/png;base64,...",
"qrExpiry": "2026-03-05T08:15:00.000Z",
"createdAt": "2026-03-05T08:00:00.000Z",
"copy": {
"amount": "100.37"
}
},
"paymentAccounts": {
"channels": {
"promptPayEnabled": true,
"bankTransferEnabled": true
},
"promptPay": {
"id": "payin_pp_uuid",
"type": "PROMPTPAY",
"name": "TopfromPAY PromptPay",
"accountNumber": "0812345678",
"bankCode": null,
"bankName": null,
"isDefault": true,
"updatedAt": "2026-03-05T07:58:00.000Z",
"copy": {
"account_number": "0812345678"
}
},
"bankAccounts": [
{
"id": "payin_bank_uuid",
"type": "BANK",
"name": "TopfromPAY SCB Main",
"accountNumber": "1234567890",
"accountName": "TopfromPAY SCB Main",
"bankCode": "014",
"bankName": "ธนาคารไทยพาณิชย์",
"isDefault": true,
"updatedAt": "2026-03-05T07:59:00.000Z",
"copy": {
"account_number": "1234567890"
}
}
]
},
"paymentContractPolicy": {
"mode": "enforce_all",
"enforceForThisMerchant": true,
"reason": "enforce_all mode",
"enforceNewMerchantFrom": null
},
"integrationWarnings": []
}
Create Payment Response Example (No QR Mode)
{
"success": true,
"transaction": {
"id": "uuid",
"orderId": "ORD-10002",
"amount": 200.55,
"fee": 4.01,
"netAmount": 196.54,
"status": "PENDING",
"paymentMethod": "PROMPTPAY",
"qrCode": null,
"qrExpiry": "2026-03-05T08:25:00.000Z",
"createdAt": "2026-03-05T08:10:00.000Z",
"copy": {
"amount": "200.55"
}
},
"paymentAccounts": {
"channels": {
"promptPayEnabled": false,
"bankTransferEnabled": true
},
"promptPay": null,
"bankAccounts": [
{
"id": "payin_bank_uuid",
"type": "BANK",
"name": "TopfromPAY SCB Main",
"accountNumber": "1234567890",
"accountName": "TopfromPAY SCB Main",
"bankCode": "014",
"bankName": "ธนาคารไทยพาณิชย์",
"isDefault": true,
"updatedAt": "2026-03-05T07:59:00.000Z",
"copy": {
"account_number": "1234567890"
}
}
]
}
}

4) สัญญา Payout / 4) Payout Contract

FieldTypeRequiredDescriptionExample
amountnumberRequiredยอดถอน (ต้องไม่น้อยกว่า minSettlementAmount ของระบบ)100
destination.bankCodestringRequiredรหัสธนาคารของปลายทาง (ดูตาราง bankCode ด้านล่าง)SCB
destination.bankNamestringOptionalชื่อธนาคารปลายทาง (ถ้าไม่ส่ง ระบบ derive จาก bankCode ที่รองรับให้)ธนาคารไทยพาณิชย์
destination.accountNumberstringRequiredเลขบัญชีปลายทาง (8-20 หลัก)4190621964
destination.accountNamestringRequiredชื่อบัญชีปลายทางเจษฎา
merchantRefstringOptionalถ้าไม่ส่ง ระบบ generate ให้ (แนะนำส่งถ้ามีเลขอ้างอิงฝั่ง merchant); รูปแบบต้องเป็น ^[A-Z0-9]{3,6}-[A-Z0-9]{3,13}$M01-WD240001
metadataobjectOptionalข้อมูลเสริมสำหรับ trace/reconcile{"sourceSystem":"lotto-main"}

กฎสั้นๆ ก่อนเรียก POST /api/v1/payout/create / Quick Rules Before POST /api/v1/payout/create

  • ต้องใช้ / Use an X-API-Key ที่เป็น secret key / that is a secret key (sk_...)
  • ตั้งค่า / Set one Idempotency-Key ต่อ 1 คำสั่งถอน (retry รายการเดิมให้ใช้ key เดิม) / per payout request and reuse it when retrying the same request.
  • ต้องส่ง / You must send X-Signature-Timestamp และ / and X-Signature
  • ต้องมี / Include Idempotency-Key (หรือ / (or idempotencyKey ใน body) / in the body)
  • ระบบจะเช็กยอดขั้นต่ำและ balance ร้านค้าก่อนรับคำสั่งถอน / The system checks minimum amounts and merchant balance before accepting the payout.
Payout Headers Quick Start (Who Generates What?)
Merchant headers backend (TopfromPAY generate ):
 
1) X-API-Key
- secret key : sk_live_... / sk_test_...
 
2) Idempotency-Key
- 1 ( UUID v4)
- retry key
 
3) X-Signature-Timestamp
- Unix time request
 
4) X-Signature
- hex(HMAC_SHA256(secret_api_key, "<timestamp>.<raw_body_json>"))
 
5) X-Signature-Nonce
- optional, recommended ( replay )
 
Payout Signature Spec
payload = "<X-Signature-Timestamp>.<raw_body_json>"
X-Signature = hex(HMAC_SHA256(secret_api_key, payload))
 
Required Headers:
- X-API-Key (secret key)
- Idempotency-Key
- X-Signature-Timestamp (unix seconds)
- X-Signature
- X-Signature-Nonce (optional, recommended)
 
Tip: GET /api/v1/payout/signature-settings
Node.js: Build Idempotency + Signature Headers
const crypto = require('crypto');
 
const secretApiKey = process.env.TopfromPAY_SECRET_KEY; // sk_live_... sk_test_...
const body = {
amount: 100,
destination: {
bankCode: 'SCB',
accountNumber: '4190621964',
accountName: '',
},
metadata: { sourceSystem: 'merchant-backoffice' },
};
 
const rawBody = JSON.stringify(body);
const timestamp = Math.floor(Date.now() / 1000).toString();
const idempotencyKey = crypto.randomUUID();
const signature = crypto
.createHmac('sha256', secretApiKey)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
 
const headers = {
'X-API-Key': secretApiKey,
'Content-Type': 'application/json',
'Idempotency-Key': idempotencyKey,
'X-Signature-Timestamp': timestamp,
'X-Signature': signature,
};
 
// fetch/axios headers + rawBody sign
POST /api/v1/payout/create
curl -X POST 'http://localhost:3001/api/v1/payout/create' \
-H 'X-API-Key: sk_test_xxxxxxxxx' \
-H 'Idempotency-Key: m01-wd-20260304-0001' \
-H 'X-Signature-Timestamp: <unix_seconds>' \
-H 'X-Signature: <hmac_sha256_hex>' \
-H 'Content-Type: application/json' \
-d '{
"amount": 100,
"destination": {
"bankCode": "SCB",
"accountNumber": "4190621964",
"accountName": "เจษฎา"
},
"metadata": {
"sourceSystem": "merchant-backoffice"
}
}'
Payout Create Success Response (201)
{
"success": true,
"object": "payout",
"idempotent": false,
"payout": {
"object": "payout",
"id": "po_123",
"merchant_id": "m_123",
"merchant_ref": "M01-WD240001",
"amount": 100,
"fee": 0,
"net_amount": 100,
"currency": "THB",
"destination": {
"bank_code": "014",
"bank_name": "ธนาคารไทยพาณิชย์",
"account_number": "4190621964",
"account_name": "เจษฎา"
},
"status": "QUEUED",
"status_reason": null,
"external_ref": null,
"requested_at": "2026-03-05T08:05:00.000Z",
"processed_at": null,
"created_at": "2026-03-05T08:05:00.000Z",
"metadata": {
"sourceSystem": "merchant-backoffice"
},
"copy": {
"amount": "100.00",
"account_number": "4190621964"
}
}
}
Payout Error Response Example
{
"error": "idempotencyKey is required (send Idempotency-Key header or idempotencyKey field)",
"code": "IDEMPOTENCY_KEY_REQUIRED",
"details": null
}

4.5) สัญญา Copy ของ Dashboard/Admin / 4.5) Dashboard/Admin Copy Contract

สำหรับ endpoint ภายในระบบ / For internal endpoints (/api/payout, /api/settlement, /api/admin/*) ที่มีจำนวนเงินหรือเลขบัญชี ระบบจะส่ง / that include money amounts or account numbers, the API returns copy field เพื่อให้ UI ใช้คัดลอกได้ทันที / fields so the UI can provide copy actions immediately.

EndpointFieldTypeAvailabilityDescriptionExample
GET /api/payoutpayouts[].copy.amountstringAlwaysคัดลอกยอด payout เป็นทศนิยม 2 ตำแหน่ง"100.00"
GET /api/payoutpayouts[].copy.account_numberstringAlwaysคัดลอกเลขบัญชีปลายทาง"4190621964"
GET /api/payout/summaryqueued|inProgress|success|failed|cancelled|total.copy.amountstringAlwaysคัดลอกยอดจากข้อมูลสรุป payout-
GET /api/settlementsettlements[].copy.amountstringAlwaysคัดลอกยอดถอนเป็นทศนิยม 2 ตำแหน่ง"1200.50"
GET /api/settlementsettlements[].copy.account_numberstringAlwaysคัดลอก bankAccount ของรายการถอน"1234567890"
GET /api/settlement/summarypending|processing|completed|failed|total.copy.amountstringAlwaysคัดลอกยอดจากข้อมูลสรุป settlement-
GET /api/admin/payoutspayouts[].copy.amount / payouts[].copy.account_numberstringAlwaysคัดลอกยอดและเลขบัญชีในหน้าจัดการ payout-
GET /api/admin/payouts/summarysummary.<status>.copy.amountstringAlwaysคัดลอกยอดในกล่อง summary แยกสถานะ-
GET /api/admin/payouts/batchesbatches[].copy.amountstringAlwaysคัดลอกยอดรวมของแต่ละ batch"2500.00"
GET /api/admin/payouts/batches/:idbatch.copy.amount / batch.payouts[].copy.*stringAlwaysคัดลอกยอด batch และข้อมูล payout ใน batch เดียวกัน-
GET /api/admin/settlementssettlements[].copy.amount / settlements[].copy.account_numberstringAlwaysคัดลอกยอดและเลขบัญชีในหน้าจัดการ settlement-
GET /api/admin/settlements/summarytotal|pending|processing|completed|failed.copy.amountstringAlwaysคัดลอกยอดสรุป settlement ฝั่งแอดมิน-
GET /api/admin/transactionstransactions[].copy.amountstringAlwaysคัดลอกยอดรับเงินในรายการธุรกรรม"100.00"
GET /api/admin/bank-accountsdata[].copy.account_numberstringAlwaysคัดลอกเลขบัญชีระบบกลาง"1234567890"
POST /api/payment/create-linktransaction.copy.amountstringAlwaysคัดลอกยอดสำหรับหน้า dashboard create link"300.00"
Copy Field Contract
copy field contract (internal APIs)
- copy.amount: string 2 "100.00"
- copy.account_number: string
- amount -> copy.amount
- accountNumber/account_number -> copy.account_number
GET /api/payout (merchant dashboard)
{
"payouts": [
{
"id": "po_001",
"merchantId": "m_123",
"merchantRef": "M01-WD240001",
"amount": "100",
"bankCode": "014",
"bankName": "ธนาคารไทยพาณิชย์",
"accountNumber": "4190621964",
"accountName": "เจษฎา",
"status": "QUEUED",
"createdAt": "2026-03-09T10:00:00.000Z",
"copy": {
"amount": "100.00",
"account_number": "4190621964"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
},
"filters": {
"status": "all"
}
}
GET /api/settlement (merchant dashboard)
{
"settlements": [
{
"id": "st_001",
"merchantId": "m_123",
"amount": "1200.5",
"fee": "12.01",
"netAmount": "1188.49",
"bankAccount": "1234567890",
"bankName": "ธนาคารไทยพาณิชย์",
"status": "PENDING",
"createdAt": "2026-03-09T10:05:00.000Z",
"copy": {
"amount": "1200.50",
"account_number": "1234567890"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
},
"filters": {
"status": "all"
}
}
GET /api/admin/payouts
{
"payouts": [
{
"id": "po_001",
"merchantId": "m_123",
"merchantRef": "M01-WD240001",
"amount": "100",
"accountNumber": "4190621964",
"status": "QUEUED",
"merchant": {
"businessName": "Demo Store",
"username": "merchant_demo"
},
"batch": null,
"copy": {
"amount": "100.00",
"account_number": "4190621964"
}
}
],
"pagination": {
"page": 1,
"limit": 100,
"total": 1,
"totalPages": 1
}
}
GET /api/admin/settlements
{
"settlements": [
{
"id": "st_001",
"merchantId": "m_123",
"amount": "1200.5",
"bankAccount": "1234567890",
"bankName": "ธนาคารไทยพาณิชย์",
"status": "PENDING",
"merchant": {
"id": "m_123",
"businessName": "Demo Store",
"username": "merchant_demo",
"balance": "9500"
},
"copy": {
"amount": "1200.50",
"account_number": "1234567890"
}
}
],
"pagination": {
"page": 1,
"limit": 20,
"total": 1,
"totalPages": 1
},
"filters": {
"status": "ALL",
"q": ""
}
}
GET /api/admin/transactions
{
"transactions": [
{
"id": "tx_001",
"merchantId": "m_123",
"orderId": "ORD-10001",
"amount": "100",
"status": "PAID",
"referenceId": "ORD-10001",
"confirmedAt": "2026-03-09T10:10:00.000Z",
"copy": {
"amount": "100.00"
}
}
],
"pagination": {
"page": 1,
"limit": 50,
"total": 1,
"totalItems": 1,
"totalPages": 1
}
}
GET /api/admin/bank-accounts
{
"success": true,
"data": [
{
"id": "acct_001",
"type": "BANK",
"usage": "PAYOUT",
"name": "SCB Debit Main",
"accountNumber": "1234567890",
"bankCode": "014",
"bankName": "ธนาคารไทยพาณิชย์",
"isDefault": true,
"copy": {
"account_number": "1234567890"
}
}
]
}

5) อ้างอิง Bank Code / 5) Bank Code Reference

destination.bankCode ส่งเป็น alias / can be sent as an alias (เช่น / e.g. SCB, KBANK) หรือเป็นเลข 3 หลักได้ / or as a 3-digit code

destination.bankName เป็น optional แล้วสำหรับ bankCode ที่ระบบรู้จัก — backend จะเติมชื่อธนาคารให้เองตามตารางด้านล่าง / is optional. For bankCode values recognized by the system, the backend derives the bank name automatically from the table below.

Input AcceptedNormalized SCB CodeBank Name (TH)Note
SCB014ธนาคารไทยพาณิชย์Siam Commercial Bank
KBANK, KASIKORN004ธนาคารกสิกรไทยKasikorn Bank
KTB, KRUNGTHAI006ธนาคารกรุงไทยKrungthai Bank
BBL, BANGKOK002ธนาคารกรุงเทพBangkok Bank
TTB011ธนาคารทีเอ็มบีธนชาตTMBThanachart Bank
BAY025ธนาคารกรุงศรีอยุธยาBank of Ayudhya
GSB030ธนาคารออมสินGovernment Savings Bank
UOB024ธนาคารยูโอบีUnited Overseas Bank
CIMB022ธนาคารซีไอเอ็มบี ไทยCIMB Thai
BAAC034ธนาคารเพื่อการเกษตรและสหกรณ์การเกษตรBank for Agriculture and Agricultural Cooperatives
GHB033ธนาคารอาคารสงเคราะห์Government Housing Bank
TISCO067ธนาคารทิสโก้TISCO Bank
KKP069ธนาคารเกียรตินาคินภัทรKiatnakin Phatra Bank
LHBANK073ธนาคารแลนด์ แอนด์ เฮ้าส์Land and Houses Bank
SME098ธนาคารพัฒนาวิสาหกิจขนาดกลางและขนาดย่อมแห่งประเทศไทยSME Development Bank
001, 002, 004 ...ตรงตามที่ส่งส่งชื่อธนาคารให้ตรงกับบัญชีปลายทางส่งเป็นเลข 3 หลักตรงๆ ได้เช่นกัน
แนวทางมาตรฐาน: ถ้าฝั่ง merchant มี master bank code อยู่แล้ว ให้ส่งเลข 3 หลักตรงๆ ได้เลย (เช่น / Recommended approach: if your merchant system already has master bank codes, send the 3-digit code directly (for example 004, 014) เพื่อลดปัญหาalias ไม่ตรงกัน / to avoid alias mismatches.

6) งานปฏิบัติการ + Webhook / 6) Operational + Webhook

เช็กลิสต์การตั้งค่า Webhook / Webhook Setup Checklist

  1. ตั้ง Callback URL ฝั่งระบบของคุณให้รับ / Configure your callback URL to accept POST และตอบ / and return 2xx เมื่อรับสำเร็จ / when accepted successfully
  2. ตั้งค่า webhook ในระบบ TopfromPAY (Dashboard > Webhooks) แล้วเลือก event ที่ต้องการ / Configure the webhook in TopfromPAY (Dashboard > Webhooks) and select the events you need.
  3. บันทึก webhook secret เพื่อตรวจลายเซ็น / Store the webhook secret and verify X-Webhook-Signature ทุกครั้ง / on every callback
  4. dedupe callback ด้วย / Deduplicate callbacks with X-Webhook-Idempotency-Key และเก็บ / and store deliveryId สำหรับ audit/replay / for audit and replay purposes

แนวทาง Firewall / Callback IP / Firewall / Callback IP Guidance

  • ถ้าปลายทางมี firewall ให้ใช้ source IP allowlist เป็นชั้นเสริมเท่านั้น ไม่ใช่กลไกหลัก / If the destination uses a firewall, treat source IP allowlists as a secondary layer, not the primary control.
  • การยืนยันหลักต้องตรวจ / Primary verification must validate X-Webhook-Signature จาก raw body ทุกครั้งก่อนประมวลผล / from the raw body before processing.
  • ถ้าต้อง allow IP จริง ให้ใช้ public egress IP ของ production runtime/NAT ที่ส่ง callback ออกไปจริง / If you must allowlist IPs, use the real public egress IP of the production runtime or NAT that sends callbacks.
  • หากมีการย้าย infra หรือเปลี่ยน outbound route ค่า IP อาจเปลี่ยนได้ / IP values can change after infrastructure moves or outbound route changes.

คู่มือขอบเขต Balance / Balance Scope Guide

แยก endpoint ให้ชัดเจน: / Separate these endpoints clearly: /balance = ยอดของร้านค้าปัจจุบันเท่านั้น / the current merchant balance only, /balance/line-summary = สรุปยอดรวมทั้งสายงาน / the aggregate balance of the entire line

Self Balance

GET /api/v1/balance

เหมาะกับ flow ตรวจยอดก่อน create payout/request approve / Best for balance checks before create payout/request approve flows

Line Summary

GET /api/v1/balance/line-summary

เหมาะกับ dashboard สรุปเครือข่าย: self, downline, totalLine / Best for dashboards that summarize self, downline, and total line balances

GET /api/v1/payment/accounts
curl 'http://localhost:3001/api/v1/payment/accounts' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Payment Accounts Response Example
{
"object": "payment_accounts",
"merchant": {
"id": "merchant_uuid",
"username": "merchant_demo",
"business_name": "Demo Store",
"status": "ACTIVE"
},
"channels": {
"promptpay_enabled": true,
"bank_transfer_enabled": true
},
"promptpay": {
"id": "payin_pp_uuid",
"type": "PROMPTPAY",
"name": "TopfromPAY PromptPay",
"account_number": "0812345678",
"bank_code": null,
"bank_name": null,
"is_default": true,
"updated_at": "2026-03-06T09:10:00.000Z"
},
"bank_accounts": [
{
"id": "payin_bank_uuid",
"type": "BANK",
"name": "TopfromPAY SCB Main",
"account_number": "1234567890",
"account_name": "TopfromPAY SCB Main",
"bank_code": "014",
"bank_name": "ธนาคารไทยพาณิชย์",
"is_default": true,
"updated_at": "2026-03-06T09:12:00.000Z"
}
],
"updated_at": "2026-03-06T09:12:00.000Z"
}
Payment Accounts Response Example (No QR Mode)
{
"object": "payment_accounts",
"merchant": {
"id": "merchant_uuid",
"username": "merchant_demo",
"business_name": "Demo Store",
"status": "ACTIVE"
},
"channels": {
"promptpay_enabled": false,
"bank_transfer_enabled": true
},
"promptpay": null,
"bank_accounts": [
{
"id": "payin_bank_uuid",
"type": "BANK",
"name": "TopfromPAY SCB Main",
"account_number": "1234567890",
"account_name": "TopfromPAY SCB Main",
"bank_code": "014",
"bank_name": "ธนาคารไทยพาณิชย์",
"is_default": true,
"updated_at": "2026-03-06T09:12:00.000Z"
}
],
"updated_at": "2026-03-06T09:12:00.000Z"
}
GET /api/v1/balance
curl 'http://localhost:3001/api/v1/balance' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Balance Response Example
{
"object": "balance",
"merchant": {
"id": "merchant_uuid",
"username": "merchant_demo",
"businessName": "Demo Store",
"status": "ACTIVE"
},
"balance": 15234.5,
"currency": "THB",
"updatedAt": "2026-03-06T09:15:00.000Z"
}
GET /api/v1/balance/line-summary
curl 'http://localhost:3001/api/v1/balance/line-summary' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Balance Line Summary Response Example
{
"object": "balance_line_summary",
"merchant": {
"id": "merchant_uuid",
"username": "merchant_demo",
"businessName": "Demo Store",
"status": "ACTIVE"
},
"scope": {
"merchantCount": 7,
"downlineCount": 6,
"directChildrenCount": 2
},
"balances": {
"self": 15234.5,
"downline": 48765.5,
"totalLine": 64000
},
"currency": "THB",
"updatedAt": "2026-03-06T09:15:00.000Z",
"lineUpdatedAt": "2026-03-06T09:16:10.000Z"
}
GET /api/v1/profile
curl 'http://localhost:3001/api/v1/profile' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Profile Response Example
{
"object": "profile",
"authenticated_with": "secret",
"authenticated_with_mode": "live",
"merchant": {
"id": "merchant_uuid",
"username": "merchant_demo",
"email": "merchant@example.com",
"business_name": "Demo Store",
"owner_name": "demo.example.com",
"phone": "0812345678",
"status": "ACTIVE",
"fee_percent": 2,
"fee_fixed": 0,
"balance": 15234.5,
"parent_merchant_id": null,
"parent_merchant": null,
"capabilities": {
"can_create_child": false,
"can_child_create_child": false,
"max_child_depth": null,
"max_children": null
},
"counters": {
"child_merchants": 0,
"webhooks": 1
},
"created_at": "2026-03-01T10:00:00.000Z",
"updated_at": "2026-03-06T09:15:00.000Z"
}
}
GET /api/v1/webhooks
curl 'http://localhost:3001/api/v1/webhooks' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Webhooks Response Example
{
"object": "list",
"merchant": {
"id": "merchant_uuid",
"username": "merchant_demo",
"business_name": "Demo Store",
"status": "ACTIVE"
},
"total": 1,
"webhooks": [
{
"object": "webhook",
"id": "wh_123",
"url": "https://merchant.example.com/api/paygate/webhook",
"events": ["payment.success", "payment.failed", "payout.success", "payout.failed"],
"is_active": true,
"created_at": "2026-03-01T10:10:00.000Z",
"updated_at": "2026-03-06T09:20:00.000Z"
}
],
"updated_at": "2026-03-06T09:20:00.000Z"
}
GET /api/v1/webhook/events
curl 'http://localhost:3001/api/v1/webhook/events' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Webhook Events Response Example
{
"object": "list",
"events": [
{ "name": "payment.created", "description": "เมื่อสร้างรายการชำระเงินใหม่" },
{ "name": "payment.success", "description": "เมื่อชำระเงินสำเร็จ" },
{ "name": "payment.failed", "description": "เมื่อชำระเงินล้มเหลวหรือถูกยกเลิก" },
{ "name": "payment.expired", "description": "เมื่อรายการหมดอายุ" },
{ "name": "settlement.created", "description": "เมื่อสร้างรายการถอนเงิน" },
{ "name": "settlement.completed", "description": "เมื่อถอนเงินสำเร็จ" },
{ "name": "payout.success", "description": "เมื่อโอนเงินให้ลูกค้าสำเร็จ" },
{ "name": "payout.failed", "description": "เมื่อโอนเงินให้ลูกค้าล้มเหลว หรือยกเลิกการถอน (status=CANCELLED)" },
{ "name": "*", "description": "รับทุก event" }
]
}
GET /api/v1/payment/order/:orderId
curl 'http://localhost:3001/api/v1/payment/order/ORD-10001' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Payment Order Response Example
{
"transaction": {
"id": "tx_123",
"merchantId": "m_123",
"orderId": "ORD-10001",
"amount": 100,
"fee": 2,
"netAmount": 98,
"paymentMethod": "PROMPTPAY",
"status": "PAID",
"paidAt": "2026-03-05T08:02:10.000Z",
"createdAt": "2026-03-05T08:00:00.000Z"
}
}
GET /api/v1/payout/ref/:merchantRef
curl 'http://localhost:3001/api/v1/payout/ref/M01-WD240001' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Payout Lookup Response Example
{
"payout": {
"object": "payout",
"id": "po_123",
"merchant_id": "m_123",
"merchant_ref": "M01-WD240001",
"amount": 100,
"fee": 0,
"net_amount": 100,
"currency": "THB",
"destination": {
"bank_code": "014",
"bank_name": "ธนาคารไทยพาณิชย์",
"account_number": "4190621964",
"account_name": "เจษฎา"
},
"status": "SUCCESS",
"status_reason": null,
"external_ref": "SCB202603050001",
"requested_at": "2026-03-05T08:05:00.000Z",
"processed_at": "2026-03-05T08:06:10.000Z",
"created_at": "2026-03-05T08:05:00.000Z",
"metadata": {
"sourceSystem": "merchant-backoffice"
},
"copy": {
"amount": "100.00",
"account_number": "4190621964"
}
}
}
GET /api/v1/payout/signature-settings
curl 'http://localhost:3001/api/v1/payout/signature-settings' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Customer Firewall Request Template
Subject: TopfromPAY Webhook Firewall Allowlist
 
Please allow inbound HTTPS callback traffic to our webhook endpoint from TopfromPAY production egress IP.
 
- Protocol: HTTPS
- Method: POST
- Source IP: <production_egress_ip>
- Path: /api/paygate/webhook
 
Important:
1. Source IP allowlist is an additional control only.
2. Our system will verify X-Webhook-Signature on every callback before processing.
3. If TopfromPAY production infrastructure or outbound routing changes, the source IP may need to be updated.
Webhook Headers (Verify Signature)
X-Webhook-Signature: <hmac_sha256_hex>
X-Webhook-Event: payment.success
X-Webhook-Timestamp: 2026-03-05T08:02:11.000Z
X-Webhook-Delivery-Id: 4b50836d-5c5b-4d74-a6e3-70b87f5dfe4f
X-Webhook-Idempotency-Key: payment.success:tx_123
X-Webhook-Id: wh_123
X-Webhook-Version: 1
Webhook Verify Example (Node.js)
import crypto from 'crypto';
 
function verifyWebhook(rawBody, signatureHeader, webhookSecret) {
const expected = crypto.createHmac('sha256', webhookSecret).update(rawBody).digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(String(signatureHeader || ''), 'utf8');
return a.length === b.length && crypto.timingSafeEqual(a, b);
}
 
// :
// 1) verify raw body
// 2) webhook secret ( sk_ API key)

Payload ของ Webhook Event / Webhook Event Payloads

Webhook Event: payment.success
{
"event": "payment.success",
"deliveryId": "4b50836d-5c5b-4d74-a6e3-70b87f5dfe4f",
"data": {
"id": "tx_123",
"transactionId": "tx_123",
"orderId": "ORD-10001",
"amount": 100,
"fee": 2,
"netAmount": 98,
"status": "PAID",
"paidAt": "2026-03-05T08:02:10.000Z",
"idempotencyKey": "payment.success:tx_123",
"idempotency_key": "payment.success:tx_123",
"customerId": "u_123",
"customer_id": "u_123",
"memberName": "นาย ทองดี ใจงาม",
"member_name": "นาย ทองดี ใจงาม",
"senderAccountNumber": "1673585481",
"sender_account_number": "1673585481",
"senderAccountLast4": "5481",
"sender_account_last4": "5481",
"senderBankCode": "GSB",
"sender_bank_code": "GSB"
},
"timestamp": "2026-03-05T08:02:11.000Z",
"webhookId": "wh_123"
}
Webhook Event: payment.failed
{
"event": "payment.failed",
"deliveryId": "80b5a8b8-88e2-45ec-8eaa-6a52d69f0d79",
"data": {
"id": "tx_124",
"transactionId": "tx_124",
"orderId": "ORD-10001",
"amount": 100,
"status": "CANCELLED",
"idempotencyKey": "payment.failed:tx_124",
"idempotency_key": "payment.failed:tx_124",
"errorMessage": "Cancelled by merchant"
},
"timestamp": "2026-03-05T08:06:10.000Z",
"webhookId": "wh_123"
}
Webhook Event: payment.expired
{
"event": "payment.expired",
"deliveryId": "1719fbdb-0b72-4c2b-8d18-1c8dcd5fd3b7",
"data": {
"id": "tx_125",
"transactionId": "tx_125",
"orderId": "ORD-10001",
"amount": 100,
"status": "EXPIRED",
"idempotencyKey": "payment.expired:tx_125",
"idempotency_key": "payment.expired:tx_125"
},
"timestamp": "2026-03-05T08:15:01.000Z",
"webhookId": "wh_123"
}
Webhook Event: settlement.created
{
"event": "settlement.created",
"deliveryId": "5dfec3c3-8d24-447f-a60e-8ef4a0d72211",
"data": {
"id": "st_123",
"settlementId": "st_123",
"amount": 500,
"fee": 8,
"netAmount": 492,
"status": "PENDING",
"createdAt": "2026-03-05T08:40:00.000Z",
"note": "Webhook test for Demo Store"
},
"timestamp": "2026-03-05T08:40:01.000Z",
"webhookId": "wh_123"
}
Webhook Event: settlement.completed
{
"event": "settlement.completed",
"deliveryId": "1254b17f-41b0-4c80-a9f1-176dd88d68fd",
"data": {
"id": "st_123",
"settlementId": "st_123",
"amount": 500,
"fee": 8,
"netAmount": 492,
"status": "COMPLETED",
"processedAt": "2026-03-05T09:15:20.000Z",
"reference": "RID-202603050001",
"note": "Batch #B20260305-001"
},
"timestamp": "2026-03-05T09:15:21.000Z",
"webhookId": "wh_123"
}
Webhook Event: payout.success
{
"event": "payout.success",
"deliveryId": "4b50836d-5c5b-4d74-a6e3-70b87f5dfe4f",
"data": {
"id": "po_123",
"payoutId": "po_123",
"merchant_ref": "M01-WD240001",
"merchantRef": "M01-WD240001",
"idempotency_key": "withdrawal-request:req_123",
"idempotencyKey": "withdrawal-request:req_123",
"status": "SUCCESS",
"amount": 100,
"external_ref": "SCB202603050001",
"externalRef": "SCB202603050001"
},
"timestamp": "2026-03-05T08:20:10.000Z",
"webhookId": "wh_123"
}
Webhook Event: payout.failed
{
"event": "payout.failed",
"deliveryId": "21f80419-4f57-4d4a-8cdd-440ad9d548f9",
"data": {
"id": "po_124",
"payoutId": "po_124",
"merchant_ref": "M01-WD240002",
"merchantRef": "M01-WD240002",
"idempotency_key": "withdrawal-request:req_124",
"idempotencyKey": "withdrawal-request:req_124",
"status": "CANCELLED",
"amount": 100,
"external_ref": null,
"externalRef": null,
"errorMessage": "Cancelled by merchant"
},
"timestamp": "2026-03-05T08:21:40.000Z",
"webhookId": "wh_123"
}

นโยบายการส่ง Webhook (พฤติกรรมปัจจุบัน: 2026-03-11) / Webhook Delivery Policy (Current Behavior: 2026-03-11)

  • TopfromPAY ส่ง callback แบบ best effort ต่อ webhook URL ที่ active และรองรับ event นั้น
  • ระบบจะคำนวณ X-Webhook-Signature จาก JSON body ที่ส่งจริง (raw payload เดียวกัน)
  • ทุก callback มี deliveryId ใหม่เสมอเมื่อ replay/manual resend แต่ X-Webhook-Idempotency-Key จะคงเดิมต่อ business event เดิม
  • เมื่อ retry/replay จาก queue ระบบจะ re-sign ด้วย webhook secret ล่าสุดก่อนส่งทุกครั้ง
  • กรณียกเลิกถอน (CANCELLED) จะส่งผ่าน event payout.failed โดย payload มี data.status=CANCELLED
  • Webhook payout ส่งทั้งคีย์ canonical และ alias (id+payoutId, merchant_ref+merchantRef, idempotency_key+idempotencyKey)
  • Timeout ต่อคำขอ default 10 วินาที (ปรับได้ด้วย WEBHOOK_TIMEOUT_MS)
  • มี retry แบบ exponential backoff ตามค่า WEBHOOK_MAX_ATTEMPTS (default 3)
  • ฝั่ง merchant ควร queue งานภายในและตอบ 2xx ให้เร็วที่สุด

7) เมทริกซ์ฟิลด์ Webhook / 7) Webhook Field Matrix

ตารางนี้สรุป field ที่ควรรองรับใน callback payload แยกตาม event เพื่อให้ทีม backend mapping ได้ชัดเจน / This table summarizes callback payload fields by event so backend teams can map them clearly.

EventFieldTypeRequiredDescriptionExample
* (all events)eventstringRequiredชื่อ event ที่ส่งมาpayment.success
* (all events)deliveryIdstringRequiredรหัส delivery ของ callback ครั้งนี้ (ใหม่ทุก replay/send)4b50836d-5c5b-4d74-a6e3-70b87f5dfe4f
* (all events)timestampstring (ISO-8601)Requiredเวลาที่สร้าง callback payload2026-03-05T08:02:11.000Z
* (all events)webhookIdstringRequiredID ของ webhook config ในระบบ TopfromPAYwh_123
payment.successdata.idstringRequiredID รายการรับชำระ (alias ของ transactionId)tx_123
payment.successdata.transactionIdstringRequiredID รายการรับชำระtx_123
payment.successdata.orderIdstringRequiredorderId จากฝั่ง merchantORD-10001
payment.successdata.amountnumberRequiredยอดที่ชำระ100
payment.successdata.statusstringRequiredสถานะรายการPAID
payment.successdata.paidAtstring (ISO-8601)Requiredเวลาชำระสำเร็จ2026-03-05T08:02:10.000Z
payment.successdata.customerId | data.customer_idstringOptionalรหัสสมาชิก/ลูกค้าที่ merchant ส่งมาตอน create payment ถ้ามีu_123
payment.successdata.idempotency_key | data.idempotencyKeystringRecommendedคีย์กันซ้ำฝั่ง callback (รูปแบบ `payment.success:<transactionId>`)payment.success:tx_123
payment.successdata.memberName | data.member_namestringOptionalชื่อสมาชิกที่ merchant ส่งมาตอน create payment ถ้ามีนาย ทองดี ใจงาม
payment.successdata.senderAccountNumber | data.sender_account_numberstringOptionalเลขบัญชีผู้โอนที่ merchant ส่งมาตอน create payment ถ้ามี1673585481
payment.successdata.senderAccountLast4 | data.sender_account_last4stringOptionalเลขท้ายบัญชีผู้โอน 4 หลัก ระบบจะเติมให้อัตโนมัติเมื่อคำนวณได้จาก senderAccountNumber5481
payment.successdata.senderBankCode | data.sender_bank_codestringOptionalรหัสธนาคารผู้โอนที่ merchant ส่งมาตอน create payment ถ้ามีGSB
payment.failed | payment.expireddata.idstringRequiredID รายการรับชำระ (alias ของ transactionId)tx_124
payment.failed | payment.expireddata.transactionIdstringRequiredID รายการรับชำระtx_124
payment.failed | payment.expireddata.orderIdstringRequiredorderId จากฝั่ง merchantORD-10002
payment.failed | payment.expireddata.amountnumberRequiredยอดรายการ100
payment.failed | payment.expireddata.idempotency_key | data.idempotencyKeystringRecommendedคีย์กันซ้ำฝั่ง callback (รูปแบบ `<event>:<transactionId>`)payment.failed:tx_124
payment.failed | payment.expireddata.statusstringRequiredสถานะรายการ (FAILED / CANCELLED / EXPIRED)FAILED / CANCELLED / EXPIRED
payment.faileddata.errorMessagestringRecommendedเหตุผลล้มเหลวหรือยกเลิกCancelled by merchant
settlement.created | settlement.completeddata.id | data.settlementIdstringRequiredID รายการถอนเครดิต (alias กันเอง)st_123
settlement.created | settlement.completeddata.amountnumberRequiredยอดถอนเครดิต500
settlement.created | settlement.completeddata.fee | data.netAmountnumberRecommendedค่าธรรมเนียมและยอดสุทธิ (มีใน payload มาตรฐานของระบบ)fee=8, netAmount=492
settlement.createddata.status | data.createdAt | data.notestringRecommendedสถานะเริ่มต้น, เวลาเริ่มรายการ, และหมายเหตุ (ถ้ามี)status=PENDING
settlement.completeddata.status | data.processedAt | data.reference | data.notestringRecommendedสถานะสำเร็จ, เวลาที่ประมวลผลเสร็จ, เลขอ้างอิง, หมายเหตุstatus=COMPLETED
payout.success | payout.failed (includes CANCELLED)webhookIdstringRequiredID ของ webhook endpoint ที่ถูกใช้ส่ง callbackwh_123
payout.success | payout.failed (includes CANCELLED)data.idstringRequiredID payoutpo_123
payout.success | payout.failed (includes CANCELLED)data.payoutIdstringRequiredID payout (alias ของ data.id)po_123
payout.success | payout.failed (includes CANCELLED)data.merchant_refstringRequiredmerchant reference ของคำสั่งถอนM01-WD240001
payout.success | payout.failed (includes CANCELLED)data.merchantRefstringRequiredmerchant reference (alias ของ data.merchant_ref)M01-WD240001
payout.success | payout.failed (includes CANCELLED)data.idempotency_key | data.idempotencyKeystring | nullRecommendedidempotency key เดิมจากตอน create payout/request approve ถ้ามีwithdrawal-request:req_123
payout.success | payout.failed (includes CANCELLED)data.statusstringRequiredสถานะ payoutSUCCESS / FAILED / CANCELLED
payout.success | payout.failed (includes CANCELLED)data.amountnumberRequiredยอดถอน100
payout.failed (FAILED/CANCELLED)data.errorMessagestringRecommendedเหตุผลความล้มเหลวหรือการยกเลิกCancelled by merchant
payout.success | payout.failed (includes CANCELLED)data.external_ref | data.externalRefstring | nullOptionalเลขอ้างอิงภายนอกของการโอน (ถ้ามี)SCB202603050001

8) แคตตาล็อก Error (Payment + Payout) / 8) Error Catalog (Payment + Payout)

สรุป error ที่พบบ่อยจาก endpoint หลัก เพื่อให้ทำ error handling ได้ครบตั้งแต่รอบแรก / Common endpoint errors, summarized so your error handling is complete from the first integration pass.

POST /api/v1/payment/create

CodeHTTPWhen
Invalid amount400amount ไม่ถูกต้องหรือ <= 0
ยอดชำระขั้นต่ำคือ <min> บาท400amount ต่ำกว่าค่า minPaymentAmount
ยอดชำระสูงสุดคือ <max> บาท400amount เกินค่า maxPaymentAmount
PAYMENT_CONTRACT_POLICY_VIOLATION400ไม่ส่งข้อมูลตาม Create Payment Contract ที่ระบบบังคับ
Order ID already exists400orderId ซ้ำภายใน merchant เดียวกัน
Forbidden403ใช้ key ที่ไม่ใช่ secret key (sk_...) กับ endpoint แบบเขียนข้อมูล
Failed to create payment500ข้อผิดพลาดภายในระบบ

GET /api/v1/payment/order/:orderId

CodeHTTPWhen
Transaction not found404ไม่พบรายการตาม orderId ของ merchant นี้
Failed to get payment500ข้อผิดพลาดภายในระบบ

Payout Endpoints / Payout Endpoints

CodeHTTPWhen
IDEMPOTENCY_KEY_REQUIRED400ไม่ส่ง Idempotency-Key สำหรับ payout endpoint
INVALID_MERCHANT_REF_FORMAT400merchantRef ไม่ตรงรูปแบบ ^[A-Z0-9]{3,6}-[A-Z0-9]{3,13}$ (PREFIX 3-6, REFERENCE 3-13)
VALIDATION_ERROR400ข้อมูล request ไม่ครบหรือไม่ถูกต้อง
INSUFFICIENT_BALANCE400ยอดคงเหลือ merchant ไม่พอ
DUPLICATE_PAYOUT_REQUEST409merchantRef/idempotency ซ้ำกับคำขอเดิม

9) โฟลว์เชื่อมต่อ End-to-End / 9) End-to-End Integration Flow

  1. 1Merchant backend สร้าง payment ผ่าน / Merchant backend creates a payment through POST /api/v1/payment/create
  2. 2เมื่อมีผลชำระ TopfromPAY ส่ง webhook callback / When payment status changes, TopfromPAY sends a webhook callback (payment.success|failed|expired) ไปยัง callback URL / to your callback URL
  3. 3Merchant backend ยืนยันสถานะซ้ำผ่าน / Merchant backend confirms status again through GET /api/v1/payment/order/:orderId ก่อน credit/fulfillment / before crediting or fulfillment
  4. 4เมื่อมีคำสั่งถอน ให้เรียก / When a payout is requested, call POST /api/v1/payout/create พร้อม signature และ idempotency / with signature and idempotency
  5. 5รอ webhook / Wait for the payout.success|failed แล้วค่อยอัปเดตสถานะฝั่งสมาชิก / webhook before updating member state(ยกเลิกถอนจะมาเป็น / cancellations arrive as payout.failed + status=CANCELLED)
Step 1: Create Payment
curl -X POST 'http://localhost:3001/api/v1/payment/create' \
-H 'X-API-Key: sk_test_xxxxxxxxx' \
-H 'Content-Type: application/json' \
-d '{
"amount": 100.37,
"orderId": "ORD-10001",
"description": "Topup",
"metadata": {
"customerId": "u_123",
"senderAccountNumber": "1673585481",
"senderBankCode": "GSB",
"memberName": "นาย ทองดี ใจงาม",
"memberNameEn": "THONGDEE JAINGAM"
}
}'
Step 2: Receive payment.success Webhook
{
"event": "payment.success",
"deliveryId": "4b50836d-5c5b-4d74-a6e3-70b87f5dfe4f",
"data": {
"id": "tx_123",
"transactionId": "tx_123",
"orderId": "ORD-10001",
"amount": 100,
"fee": 2,
"netAmount": 98,
"status": "PAID",
"paidAt": "2026-03-05T08:02:10.000Z",
"idempotencyKey": "payment.success:tx_123",
"idempotency_key": "payment.success:tx_123",
"customerId": "u_123",
"customer_id": "u_123",
"memberName": "นาย ทองดี ใจงาม",
"member_name": "นาย ทองดี ใจงาม",
"senderAccountNumber": "1673585481",
"sender_account_number": "1673585481",
"senderAccountLast4": "5481",
"sender_account_last4": "5481",
"senderBankCode": "GSB",
"sender_bank_code": "GSB"
},
"timestamp": "2026-03-05T08:02:11.000Z",
"webhookId": "wh_123"
}
Step 3: Confirm By OrderId
curl 'http://localhost:3001/api/v1/payment/order/ORD-10001' \
-H 'X-API-Key: sk_test_xxxxxxxxx'
Step 4: Create Payout
curl -X POST 'http://localhost:3001/api/v1/payout/create' \
-H 'X-API-Key: sk_test_xxxxxxxxx' \
-H 'Idempotency-Key: m01-wd-20260304-0001' \
-H 'X-Signature-Timestamp: <unix_seconds>' \
-H 'X-Signature: <hmac_sha256_hex>' \
-H 'Content-Type: application/json' \
-d '{
"amount": 100,
"destination": {
"bankCode": "SCB",
"accountNumber": "4190621964",
"accountName": "เจษฎา"
},
"metadata": {
"sourceSystem": "merchant-backoffice"
}
}'
Step 5: Receive payout.success Webhook
{
"event": "payout.success",
"deliveryId": "4b50836d-5c5b-4d74-a6e3-70b87f5dfe4f",
"data": {
"id": "po_123",
"payoutId": "po_123",
"merchant_ref": "M01-WD240001",
"merchantRef": "M01-WD240001",
"idempotency_key": "withdrawal-request:req_123",
"idempotencyKey": "withdrawal-request:req_123",
"status": "SUCCESS",
"amount": 100,
"external_ref": "SCB202603050001",
"externalRef": "SCB202603050001"
},
"timestamp": "2026-03-05T08:20:10.000Z",
"webhookId": "wh_123"
}

10) Interactive Docs (ลองยิง API สด) / 10) Interactive Docs (Try API Live)

ใช้สำหรับทดสอบเชื่อมต่อแบบเร็วจากหน้า docs โดยตรง (ควรใช้ test key/สภาพแวดล้อมทดสอบ) / Use this to run quick integration tests directly from the docs page (prefer test keys or a test environment).

ผลลัพธ์ / Response

ยังไม่มีผลลัพธ์ / No results yet

กด Send Request เพื่อทดสอบ / Press Send Request to test

11) ชุดเริ่มต้นการเชื่อมต่อ / 11) Integration Starter Pack

สำหรับทีม dev ที่ต้องการเชื่อมระบบเร็ว: ใช้ SDK + Postman ด้านล่าง แล้วเริ่ม flow แบบ / For teams that want the fastest path to integration, use the SDKs and Postman collection below, then start with the flow create payment → /pay/:id → webhook

Starter Flow (Hosted Checkout UI)
Quick Flow ():
 
1) Merchant backend POST /api/v1/payment/create
2) transaction.id response
3) : https://TopfromPAY.com/pay/<transaction.id>
4) UI TopfromPAY
5) merchant webhook payment.success/payment.failed/payment.expired
 
:
- UI
- /api/payment/create-link route dashboard session public API key flow
Node.js Starter: Create Payment + Build /pay/:id URL
const TopfromPAYClient = require('./paygate-node');
 
const client = new TopfromPAYClient({
apiKey: process.env.TopfromPAY_API_KEY, // sk_live_...
baseUrl: 'http://localhost:3001/api/v1',
});
 
async function createDepositLink() {
const result = await client.createPayment({
amount: 100,
orderId: 'ORD-10001',
description: 'Topup',
metadata: {
senderAccountNumber: '1673585481',
senderBankCode: 'GSB',
memberName: ' ',
memberNameEn: 'THONGDEE JAINGAM',
customerId: 'u_123',
},
});
 
const paymentUrl = TopfromPAYClient.buildHostedPaymentUrl({
frontendBaseUrl: 'https://TopfromPAY.com',
transactionId: result.transaction.id,
});
 
return {
paymentUrl,
transactionId: result.transaction.id,
orderId: result.transaction.orderId,
};
}
Node.js Starter: Verify Webhook Signature
const express = require('express');
const TopfromPAYClient = require('./paygate-node');
 
const app = express();
app.use(
express.json({
verify: (req, _res, buf) => {
req.rawBody = buf.toString('utf8');
},
})
);
 
app.post('/api/paygate/webhook', (req, res) => {
const signatureHeader = req.header('X-Webhook-Signature') || '';
const ok = TopfromPAYClient.verifyWebhookSignature({
rawBody: req.rawBody || '',
signatureHeader,
webhookSecret: process.env.TopfromPAY_WEBHOOK_SECRET,
});
 
if (!ok) return res.status(401).json({ error: 'invalid signature' });
 
// TODO: dedupe X-Webhook-Idempotency-Key (fallback: event+transactionId deliveryId)
// map payoutId/orderId
return res.status(200).json({ received: true });
});
Node.js Starter: Create Payout (auto sign)
const TopfromPAYClient = require('./paygate-node');
const client = new TopfromPAYClient({ apiKey: process.env.TopfromPAY_API_KEY });
 
async function createPayout() {
return client.createPayout({
amount: 100,
destination: {
bankCode: 'SCB',
accountNumber: '4190621964',
accountName: '',
},
metadata: {
sourceSystem: 'merchant-backoffice',
memberId: 'member_1001',
},
});
}

12) พื้นฐานด้านความปลอดภัย / 12) Security Baseline

  • 🔑เก็บ / Keep sk_... เฉพาะฝั่ง server (เช่น backend) ห้ามฝังใน frontend/browser / only on the server side (for example backend). Never embed it in the frontend or browser.
  • 🔒เปิดใช้ HTTPS และจำกัด IP/Origin ตามโครงสร้างระบบของคุณ — สำหรับ webhook ให้ถือ / Use HTTPS and restrict IP/origin access as appropriate for your architecture. For webhooks, treat signature verification เป็นชั้นหลัก และ IP allowlist เป็นชั้นรอง / as the primary control and IP allowlists as secondary.
  • 🔄ใช้ idempotency และ signature ทุกคำสั่งถอนเงิน / Use idempotency and signatures for every payout request
  • 📋กำหนด rotation policy สำหรับ API key และ webhook secret/signature key / Define a rotation policy for API keys and webhook secrets/signing keys
© 2026 TopfromPAY · v2026.04.21อัปเดตล่าสุด April 21, 2026 / Last updated April 21, 2026