Authentication & Signature
Authentication & Signature
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 |
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:
คำนวณดังนี้:
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
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:
สมมติ:
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:
- Verify HTTP method is
POST. - เช็คว่า HTTP method เป็น
POST - Read raw body bytes (before any JSON parsing).
- อ่าน raw body bytes (ก่อน parse JSON)
- Look up the merchant by the
merchant_idin the body and verifytokenmatches. - ค้นหา merchant ด้วย
merchant_idจาก body และเช็คtokenตรงกัน - Compute
HMAC-SHA256(raw_body, merchant.secret)and compare againstX-SIGNATURE. - คำนวณ
HMAC-SHA256(raw_body, merchant.secret)เทียบกับX-SIGNATURE - If an IP whitelist is configured, verify the source IP is in the list.
- ถ้ามี IP whitelist ตรวจ source IP ว่าอยู่ในรายการ
- If all checks pass, route to the endpoint handler.
- ผ่านทุกข้อแล้วจึงส่งต่อให้ 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) ต้องส่งรหัสธนาคารตามตารางนี้ ตัวพิมพ์ใหญ่
| Code | Bank | รหัส | ธนาคาร |
|---|---|---|---|
BAAC | Bank for Agriculture and Agricultural Cooperatives | BAAC | ธนาคารเพื่อการเกษตรและสหกรณ์การเกษตร (ธ.ก.ส.) |
BAY | Bank of Ayudhya (Krungsri) | BAY | ธนาคารกรุงศรีอยุธยา |
BBL | Bangkok Bank | BBL | ธนาคารกรุงเทพ |
CIMB | CIMB Thai | CIMB | ธนาคารซีไอเอ็มบีไทย |
CITI | Citibank | CITI | ธนาคารซิตี้แบงก์ |
GHB | Government Housing Bank | GHB | ธนาคารอาคารสงเคราะห์ |
GSB | Government Savings Bank | GSB | ธนาคารออมสิน |
KBANK | Kasikornbank | KBANK | ธนาคารกสิกรไทย |
KK | Kiatnakin Phatra Bank | KK | ธนาคารเกียรตินาคินภัทร |
KTB | Krungthai Bank | KTB | ธนาคารกรุงไทย |
LH | Land and Houses Bank | LH | ธนาคารแลนด์ แอนด์ เฮ้าส์ |
SC | Standard Chartered | SC | ธนาคารสแตนดาร์ดชาร์เตอร์ด |
SCB | Siam Commercial Bank | SCB | ธนาคารไทยพาณิชย์ |
SCIB | Siam City Bank | SCIB | ธนาคารนครหลวงไทย |
TISCO | Tisco Bank | TISCO | ธนาคารทิสโก้ |
TTB | TMBThanachart Bank | TTB | ธนาคารทหารไทยธนชาต |
UOB | UOB Thailand | UOB | ธนาคารยูโอบี |
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") — รายการธนาคารที่รองรับอาจเพิ่มในอนาคต