Required headers

Headers ที่ต้องส่ง

Header Required บังคับ Description คำอธิบาย
Content-Type Yes Must be application/json ต้องเป็น application/json
X-SIGNATURE Yes HMAC-SHA256 of the raw request body, hex-encoded. HMAC-SHA256 ของ raw request body แสดงเป็น hex
No Bearer token, no API-key header
ไม่ต้องส่ง Bearer token หรือ API-key header

Identity is established entirely through the token in the body and the HMAC signature of that body. Only the two headers above are required.

ระบบยืนยันตัวตนผ่าน token ใน body และ HMAC signature ของ body นั้นเท่านั้น — ส่งเฉพาะ 2 headers ตามตารางด้านบนก็พอ

Required body fields

Body fields ที่ต้องส่ง

Every authenticated endpoint expects at least these three fields. Specific endpoints add more — see each endpoint page for the full schema.

ทุก endpoint ที่ต้องยืนยันตัวตนจะต้องมีสามฟิลด์นี้เป็นอย่างน้อย — แต่ละ endpoint จะมีฟิลด์เพิ่มเติม ดูได้ที่หน้า endpoint แต่ละหน้า

Field Type Required Notes ฟิลด์ ประเภท บังคับ หมายเหตุ
merchant_id string Yes Alphanumeric, must end with a digit. Issued by your payment gateway admin. merchant_id string Yes Alphanumeric ลงท้ายด้วยตัวเลข ออกโดย Payment gateway admin
token string Yes Your API token. Must match exactly the value issued to you. token string Yes API token ต้องตรงกับค่าที่ออกให้คุณเป๊ะ ๆ
time number | string Yes Unix epoch (seconds) — e.g. 1656272222. Use your client system's current time at the moment the request is sent (compute it freshly per request, don't hardcode it). Examples: Math.floor(Date.now()/1000) (Node), time() (PHP), $(date +%s) (bash). time number | string Yes Unix epoch (วินาที) — เช่น 1656272222 — ใช้ เวลาปัจจุบัน ของระบบฝั่งคุณ ณ ตอนที่ส่ง request (คำนวณใหม่ทุกครั้ง อย่า hardcode ค่าเดิม) — ตัวอย่าง: Math.floor(Date.now()/1000) (Node), time() (PHP), $(date +%s) (bash)

Signature algorithm

Algorithm สำหรับ Signature

Compute:

คำนวณดังนี้:

Definition
X-SIGNATURE = lowercase_hex( HMAC-SHA256( key = secret, message = raw_request_body ) )

Critical rules

กฎสำคัญ

  • Sign the exact bytes you send. Build the JSON string once, sign that string, and send that same string in the body. Do not parse and re-stringify.
  • เซ็น bytes เดียวกับที่จะส่ง — สร้าง JSON string ครั้งเดียว เซ็นจาก string นั้น แล้วส่ง string เดียวกันลง body — ห้าม parse แล้ว stringify ใหม่
  • UTF-8 encoding. Strings are encoded as UTF-8 before HMAC.
  • UTF-8 — string ถูก encode เป็น UTF-8 ก่อนเข้า HMAC
  • Lowercase hex output. 64 hex characters (a–f, 0–9).
  • Hex ตัวเล็ก — output 64 ตัวอักษร (a–f, 0–9)
  • Field order matters at the byte level. The signature is over bytes, so any reordering or whitespace change invalidates it.
  • ลำดับ field มีผลในระดับ byte — เพราะ signature คำนวณจาก bytes การสลับ field หรือเปลี่ยน whitespace จะทำให้ invalid
Common pitfall: re-serializing the body
ข้อผิดพลาดที่เจอบ่อย: serialize body ซ้ำ

Frameworks like Express, Laravel, and Django often parse JSON to objects and re-serialize before sending. The two strings may differ in whitespace, key order, or unicode escaping — causing signature-error 403. Always send the raw string you signed.

Framework เช่น Express, Laravel, Django มักจะ parse JSON เป็น object แล้ว serialize ใหม่ก่อนส่ง — string ที่ออกมาอาจต่างกันที่ whitespace, ลำดับ key หรือ unicode escape → ทำให้ได้ signature-error 403 — ต้องส่ง raw string เดียวกับที่เซ็น

Signature examples

ตัวอย่างการเซ็น

Given:

สมมติ:

Inputs
secret = "s3cr3t-key-xyz"
body   = {"merchant_id":"AA12345678","token":"abc-token-123","time":"1746692400"}

The expected signature is the HMAC-SHA256 of the body string keyed by the secret.

Signature ที่คาดหวังคือ HMAC-SHA256 ของ body string โดยใช้ secret เป็น key

TIME=$(date +%s)
BODY="{\"merchant_id\":\"AA12345678\",\"token\":\"abc-token-123\",\"time\":$TIME}"
SECRET='s3cr3t-key-xyz'

# Compute signature
SIG=$(printf '%s' "$BODY" | openssl dgst -sha256 -hmac "$SECRET" | awk '{print $2}')
echo "$SIG"

# Send request
curl -X POST https://<your-api-domain>/balance \
  -H "Content-Type: application/json" \
  -H "X-SIGNATURE: $SIG" \
  -d "$BODY"
import crypto from 'node:crypto';

const secret = 's3cr3t-key-xyz';

// IMPORTANT: build the JSON string once and reuse it
const body = JSON.stringify({
  merchant_id: 'AA12345678',
  token:       'abc-token-123',
  time:       Math.floor(Date.now() / 1000),
});

const signature = crypto.createHmac('sha256', secret)
                        .update(body, 'utf8')
                        .digest('hex');

const r = await fetch('https://<your-api-domain>/balance', {
  method:  'POST',
  headers: {
    'Content-Type': 'application/json',
    'X-SIGNATURE':  signature,
  },
  body,                       // send the SAME string we signed
});
console.log(r.status, await r.json());
<?php
$secret = 's3cr3t-key-xyz';

// Build body once and sign that exact string
$body = json_encode(
    ['merchant_id' => 'AA12345678', 'token' => 'abc-token-123', 'time' => time()],
    JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES
);

$signature = hash_hmac('sha256', $body, $secret);

$ch = curl_init('https://<your-api-domain>/balance');
curl_setopt_array($ch, [
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_POST           => true,
    CURLOPT_POSTFIELDS     => $body,
    CURLOPT_HTTPHEADER     => [
        'Content-Type: application/json',
        "X-SIGNATURE: $signature",
    ],
]);
$resp = curl_exec($ch);
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
echo "HTTP $code\n$resp\n";

IP whitelist (optional)

IP Whitelist (ถ้าเปิดใช้)

Your payment gateway admin may configure a list of allowed source IP addresses for your merchant. When set, requests from any other IP are rejected with 403 ip-not-whitelisted. If you don't see this error, you don't have a whitelist configured.

Payment gateway admin อาจตั้งค่า IP whitelist สำหรับ merchant ของคุณไว้ — ถ้าตั้งไว้ request จาก IP อื่นจะถูกปฏิเสธด้วย 403 ip-not-whitelisted ถ้าไม่เคยเจอ error นี้ แปลว่าฝั่งคุณยังไม่มี whitelist

If you operate behind NAT or a CDN, ensure the egress IP your servers actually use is the one registered. If you need to update the whitelist, contact your payment gateway admin.

ถ้าระบบคุณอยู่หลัง NAT หรือ CDN ต้องมั่นใจว่า egress IP ตรงกับที่ลงทะเบียนไว้ — หากต้องเปลี่ยนรายการ ให้ติดต่อ Payment gateway admin

How we verify your request (server-side)

วิธีที่เราใช้ตรวจสอบ request (ฝั่ง server)

For reference — this is the algorithm our server runs on every authenticated request:

เพื่อให้เห็นภาพ — นี่คือ algorithm ที่ server เราใช้ตรวจทุก request:

  1. Verify HTTP method is POST.
  2. เช็คว่า HTTP method เป็น POST
  3. Read raw body bytes (before any JSON parsing).
  4. อ่าน raw body bytes (ก่อน parse JSON)
  5. Look up the merchant by the merchant_id in the body and verify token matches.
  6. ค้นหา merchant ด้วย merchant_id จาก body และเช็ค token ตรงกัน
  7. Compute HMAC-SHA256(raw_body, merchant.secret) and compare against X-SIGNATURE.
  8. คำนวณ HMAC-SHA256(raw_body, merchant.secret) เทียบกับ X-SIGNATURE
  9. If an IP whitelist is configured, verify the source IP is in the list.
  10. ถ้ามี IP whitelist ตรวจ source IP ว่าอยู่ในรายการ
  11. If all checks pass, route to the endpoint handler.
  12. ผ่านทุกข้อแล้วจึงส่งต่อให้ endpoint handler

Authentication error codes

Error code ของ Authentication

HTTP Error code Meaning Error code ความหมาย
405 method-not-allowed Used a non-POST method on a POST-only endpoint. method-not-allowed ใช้ method อื่นที่ไม่ใช่ POST
403 authentication-failed Missing/invalid merchant_id, or token doesn't match, or merchant not found. authentication-failed merchant_id ไม่ถูกต้อง หรือ token ไม่ตรง หรือไม่พบ merchant
403 signature-required Missing X-SIGNATURE header. signature-required ไม่มี header X-SIGNATURE
403 signature-error Signature does not match the body. Most often caused by re-serializing the body before sending. signature-error Signature ไม่ตรงกับ body — สาเหตุหลักคือ serialize body ใหม่ก่อนส่ง
403 ip-not-whitelisted Your source IP isn't in the configured whitelist. ip-not-whitelisted Source IP ของคุณไม่อยู่ใน whitelist
400 invalid-inputs Body is missing or not valid JSON. invalid-inputs Body หายหรือไม่ใช่ JSON ที่ถูกต้อง

See Error Codes for the full reference.

ดูเพิ่มเติมที่ รหัสข้อผิดพลาด

Bank codes (THB)

รหัสธนาคาร (THB)

Endpoints that accept a bank field (/payment/create, /payment/create-transfer, /withdraw/create, /thb-settlement/create) require the canonical bank code, uppercase.

Endpoint ที่รับฟิลด์ bank (/payment/create, /payment/create-transfer, /withdraw/create, /thb-settlement/create) ต้องส่งรหัสธนาคารตามตารางนี้ ตัวพิมพ์ใหญ่

CodeBank รหัสธนาคาร
BAACBank for Agriculture and Agricultural CooperativesBAACธนาคารเพื่อการเกษตรและสหกรณ์การเกษตร (ธ.ก.ส.)
BAYBank of Ayudhya (Krungsri)BAYธนาคารกรุงศรีอยุธยา
BBLBangkok BankBBLธนาคารกรุงเทพ
CIMBCIMB ThaiCIMBธนาคารซีไอเอ็มบีไทย
CITICitibankCITIธนาคารซิตี้แบงก์
GHBGovernment Housing BankGHBธนาคารอาคารสงเคราะห์
GSBGovernment Savings BankGSBธนาคารออมสิน
KBANKKasikornbankKBANKธนาคารกสิกรไทย
KKKiatnakin Phatra BankKKธนาคารเกียรตินาคินภัทร
KTBKrungthai BankKTBธนาคารกรุงไทย
LHLand and Houses BankLHธนาคารแลนด์ แอนด์ เฮ้าส์
SCStandard CharteredSCธนาคารสแตนดาร์ดชาร์เตอร์ด
SCBSiam Commercial BankSCBธนาคารไทยพาณิชย์
SCIBSiam City BankSCIBธนาคารนครหลวงไทย
TISCOTisco BankTISCOธนาคารทิสโก้
TTBTMBThanachart BankTTBธนาคารทหารไทยธนชาต
UOBUOB ThailandUOBธนาคารยูโอบี
Unknown codes are rejected
รหัสที่ไม่รู้จักจะถูกปฏิเสธ

Sending a code not in this table fails with HTTP 422 invalid-inputs (description: "invalid bank code"). The list of supported banks may grow over time.

ถ้าส่งรหัสที่ไม่อยู่ในตารางจะได้ HTTP 422 invalid-inputs (description: "invalid bank code") — รายการธนาคารที่รองรับอาจเพิ่มในอนาคต