ศูนย์ช่วยเหลือ CheckSlips
คู่มือเชื่อมต่อ API
ส่งภาพครั้งเดียว เก็บรหัสรายการ แล้วดึงผลจนตรวจเสร็จ เลือกดูตัวอย่างและรายละเอียดที่ระบบของคุณต้องใช้
อัปเดต 12 ตุลาคม 2569 · เปิดอ่านได้โดยไม่ต้องล็อกอิน
เริ่มเชื่อมต่อใน 3 ขั้นตอน
- 1. เตรียมสิทธิ์จาก API Keys
ใช้ Tenant ID ของร้านและ Live API Key เก็บเป็น CHECKSLIPS_TENANT_ID กับ CHECKSLIPS_API_KEY บนเซิร์ฟเวอร์ ห้ามใส่คีย์ในหน้าเว็บหรือแอปที่แจกให้ลูกค้า
- 2. ส่งภาพสลิป
เรียก POST https://api.checkslips.com/v1/verify ด้วย multipart/form-data ใส่ image และ headers ด้านล่าง เมื่อได้ 202 ให้เก็บ data.verification_id ผูกกับออเดอร์ทันที
- 3. ดึงผลรายการเดิมจนเสร็จ
เรียก GET /v1/verify/{verification_id} ทุกประมาณ 3 วินาที ใช้ headers เดิม ระหว่าง pending / processing ให้แสดง “กำลังตรวจ” เมื่อได้ผลสุดท้ายจึงอ่านข้อมูล หรือใช้ Webhook แจ้งเมื่อเสร็จ
X-Tenant-ID: <Tenant ID ของร้าน>
Authorization: Bearer <Live API Key>
202 คือรับงานแล้ว และ success: true คือเรียก API สำเร็จ ทั้งสองอย่างยังไม่ใช่การยืนยันสลิป แม้ GET ได้ 200 ก็ต้องอ่าน status ใน data
ตัวอย่างส่งภาพและดึงผล · cURL / Node.js / Python / PHP
แทนไฟล์และ environment variables ด้วยค่าของระบบคุณ ตัวอย่างระบุ match_amount: 100.00 และ receiver_validation_mode: tenant อย่างชัดเจน ก่อนใช้ให้ตั้งธนาคาร เลขบัญชี และชื่อผู้รับประจำร้านให้ตรงกับปลายทางของออเดอร์ หากไม่ส่ง match_amount ผลจะยังไม่พร้อมสำหรับยืนยันออเดอร์อัตโนมัติ
# Bash: set CHECKSLIPS_TENANT_ID and CHECKSLIPS_API_KEY on your server first.
curl --fail-with-body --max-time 90 https://api.checkslips.com/v1/verify \
-H "X-Tenant-ID: $CHECKSLIPS_TENANT_ID" \
-H "Authorization: Bearer $CHECKSLIPS_API_KEY" \
-F "image=@/path/to/slip.jpg" \
-F "match_amount=100.00" \
-F "receiver_validation_mode=tenant" \
-F "remark=order-123"
# Save data.verification_id from the response, then set VERIFICATION_ID.
# Repeat this GET every 3 seconds while status is pending or processing.
curl --fail-with-body --max-time 20 \
"https://api.checkslips.com/v1/verify/$VERIFICATION_ID" \
-H "X-Tenant-ID: $CHECKSLIPS_TENANT_ID" \
-H "Authorization: Bearer $CHECKSLIPS_API_KEY"ตัวอย่าง Node.js / Python / PHP รอผลหน้าเว็บประมาณ 60 วินาที แล้วคืนสถานะล่าสุด หากยัง processing ให้แสดงว่ารอผลและเรียก GET ด้วยรหัสเดิมต่อจากงานเบื้องหลังหรือรอบถัดไป นี่ไม่ใช่เวลาประมวลผลสูงสุดของระบบ บางรายการอาจใช้เวลานานกว่านั้น
ให้บันทึก verification_id ในฐานข้อมูลก่อนรอผล และจัดการข้อผิดพลาดโดยยังเก็บรหัสไว้ ตัวอย่างเป็นส่วนเชื่อมต่อ ยังต้องเพิ่มการผูกออเดอร์ การเก็บรหัส และการยืนยันออเดอร์ของร้าน
ตัวอย่าง JSON: รับงานแล้ว และผลตรวจสุดท้าย
ตัวอย่างสมมติ ย่อให้เห็นฟิลด์หลัก ผลจริงอาจมีฟิลด์เพิ่มหรือข้อมูลเป็น null
{
"success": true,
"data": {
"verification_id": "11111111-2222-4333-8444-555555555555",
"status": "processing",
"estimated_completion_ms": 60000,
"expected_receiver_commitment": null
},
"message": "Verification queued successfully"
}ตัวอย่างผลนี้ใช้ receiver_validation_mode: none จึงยืนยันสลิปได้โดยไม่ยืนยันเงินเข้าบัญชีร้าน ค่า estimated_completion_ms เป็นค่าประมาณ ไม่ใช้เป็นเส้นตายเพื่อตัดสินผล
ฟิลด์ที่ส่งได้ และโหมดตรวจบัญชีผู้รับ
image: ภาพ JPG / PNG / WebP ไม่เกิน 5 MB ใช้ภาพต้นฉบับเต็มใบmatch_amount: ยอดที่คาดหวัง ถ้าไม่ส่งจะไม่เทียบกับยอดออเดอร์remark: หมายเหตุอ้างอิงออเดอร์ ไม่เกิน 255 ตัวอักษรcheck_duplicate: ค่าเริ่มต้น true ควรเปิดไว้ การตรวจซ้ำช่วยป้องกันการใช้สลิปเดิมหลายครั้งreceiver_validation_mode: ไม่ส่ง = ค่าประจำร้าน; none = ตรวจเฉพาะสลิป; tenant = ตรวจผู้รับจากบัญชีร้าน; request = ระบุผู้รับสำหรับคำขอนี้
โหมด request ต้องส่ง expected_receiver_bank_code, expected_receiver_account_number และ expected_receiver_account_name ให้ครบ ไม่ส่งฟิลด์ผู้รับเหล่านี้ร่วมกับ none หรือ tenant ดูรูปแบบและข้อจำกัดล่าสุดใน API Reference
อย่าตั้ง Content-Type เองเมื่อใช้ FormData ให้ไลบรารีใส่ multipart boundary ให้ ระบบรับภาพจริงเป็นหลัก ฟิลด์ payload แบบเก่าเลิกใช้เป็นหลักฐานยืนยันสลิปแล้ว
อ่านผลสลิปและบัญชีผู้รับแยกกัน
ผลสุดท้ายมี verified, flagged, rejected, failed และ duplicate ส่วน pending / processing ยังต้องรอ ผล flagged ไม่ได้แปลว่าสลิปปลอม
authenticity_status: verified คือยืนยันสลิปครบ ส่วน bank_record_found: true หมายถึงธนาคารพบรายการโอน ยังใช้แทนผลยืนยันสลิปไม่ได้ ตรวจ status, flags และผลผู้รับร่วมกัน
การเติมเครดิตมีสองแบบ: ให้ CheckSlips ตรวจเงื่อนไขรับชำระครบ หรือให้ระบบร้านตรวจปลายทางและยอดเองหลังยืนยันสลิป ทั้งสองแบบต้องผูกออเดอร์เดิม ปฏิเสธ Sandbox และบันทึกเครดิตเพียงครั้งเดียว ค่า null / ไม่ทราบใช้แทนผลผ่านไม่ได้
ผูกออเดอร์ก่อนยืนยันการชำระเงิน
ใช้ยอดและปลายทางจากฐานข้อมูลร้านบนเซิร์ฟเวอร์ โหมด tenant เหมาะกับออเดอร์ที่รับเข้าบัญชีประจำร้านเดียวกัน ส่วนโหมด request ระบุผู้รับของแต่ละออเดอร์และบันทึก expected_receiver_commitment จากคำตอบ 202 ไว้พร้อม verification_id, Tenant ID และออเดอร์ ห้ามแทนค่าที่บันทึกด้วยค่าจากผลตรวจภายหลัง
ผลผู้รับจากภาพกับผลธนาคารแสดงแยกกัน เมื่อภาพตรงแต่ธนาคารยังไม่ยืนยัน receiver_assessment จะยัง unconfirmed และต้องตรวจเพิ่มเติม ความพร้อมจาก API เป็นเงื่อนไขตรวจสลิป ระบบร้านยังต้องตรวจปลายทางและยอดของออเดอร์ และป้องกันการใช้หลักฐานเดียวกันซ้ำ
เมื่อผ่านเงื่อนไข ให้ล็อกออเดอร์ ตรวจว่ายังไม่ชำระ และบันทึกออเดอร์พร้อมรายการเครดิตในธุรกรรมเดียว ตั้ง unique key ทั้ง Tenant ID + verification_id และ Tenant ID + trans_ref การส่ง Webhook ซ้ำหรือดึงผลหลายครั้งต้องคืนผลเดิมโดยไม่เพิ่มเครดิตอีก
ตัวอย่างยืนยันออเดอร์: ผู้รับเฉพาะคำขอและตัวกรองแบบปิดเมื่อข้อมูลไม่ครบ
ตัวกรองนี้สำหรับโหมด request ใช้ commitment ที่เก็บจากคำขอแรก และจำนวนเงินเป็นหน่วยสตางค์ ช่วยปฏิเสธ Sandbox, null, ผลยังไม่เสร็จ, หลักฐานผู้รับไม่ครบ และข้อมูลที่ไม่ตรงออเดอร์ ไม่ได้เชื่อมต่อหรือเพิ่มเงินจริงในกระเป๋า
# Server-side Bash. Use the order's trusted amount and destination, not browser input.
# CHECKSLIPS_EXPECTED_* must come from the merchant's stored order destination.
curl --fail-with-body --max-time 90 https://api.checkslips.com/v1/verify \
-H "X-Tenant-ID: $CHECKSLIPS_TENANT_ID" \
-H "Authorization: Bearer $CHECKSLIPS_API_KEY" \
-F "image=@/path/to/slip.jpg" \
-F "match_amount=100.00" \
-F "remark=order-123" \
-F "receiver_validation_mode=request" \
-F "expected_receiver_bank_code=$CHECKSLIPS_EXPECTED_BANK" \
-F "expected_receiver_account_number=$CHECKSLIPS_EXPECTED_ACCOUNT" \
-F "expected_receiver_account_name=$CHECKSLIPS_EXPECTED_NAME"
# Persist data.verification_id AND data.expected_receiver_commitment from this 202
# alongside the original order and tenant. Never replace them from a later result.
# Poll the SAME id using the GET example above, then call canAcceptPayment.ถ้าใช้ tenant ต้องยืนยันในระบบร้านว่าออเดอร์นี้ใช้บัญชีประจำร้านชุดใด และตรวจ receiver_assessment.mode: tenant / status: confirmed / provider_match: true ร่วมกับเงื่อนไขผลสุดท้ายและยอดข้างต้น อย่านำตัวกรอง request ไปตัดการตรวจ commitment ออกเพื่อให้ tenant ผ่าน
สลิปเข้าวอลเล็ต ต้องตั้งค่าอย่างไร
ระบบรองรับวอลเล็ต EWALLETID แบบเลขประจำวอลเล็ต 15 หลัก คนละประเภทกับเลขบัญชีธนาคาร เบอร์พร้อมเพย์ หรือเลขบัตรประชาชน แม้ชื่อผู้รับเหมือนกันก็ใช้แทนกันไม่ได้
หากต้องการตรวจเฉพาะข้อมูลการโอน ให้ส่ง receiver_validation_mode: none ระบบยังตรวจข้อมูลสลิปกับผู้ให้บริการ แต่ผลนี้ไม่ยืนยันว่าเงินเข้าร้าน และ payment_assessment.ready จะเป็น false
หากต้องการตรวจผู้รับด้วย ให้เพิ่มปลายทางประเภท MERCHANT พร้อมเลขวอลเล็ตและชื่อผู้รับจริงของร้าน หรือใช้โหมด request ระบุ expected_receiver_bank_code: MERCHANT พร้อมเลขและชื่อดังกล่าว ระบบต้องเทียบข้อมูลธนาคารกับภาพและปลายทางเดียวกันได้ครบก่อนผ่าน
สำหรับยืนยันออเดอร์อัตโนมัติ ให้ระบุ match_amount และเปิด check_duplicate: true รวมทั้งตรวจเงื่อนไขรับชำระทั้งหมดด้านบน ห้ามใช้เพียงชื่อผู้รับหรือ bank_record_found: true เพื่อเติมเครดิต
ร้านตรวจปลายทางและเติมเครดิตเอง · โหมด none
เหมาะกับร้านที่มีระบบตรวจบัญชีผู้รับ ยอดออเดอร์ และเครดิตของตัวเอง ใช้ status และ authenticity_status เป็น verified, bank_record_found: true, is_sandbox: false และ is_duplicate: false จาก API ที่ร้านเรียกเอง
payment_assessment.ready เป็น false ตามปกติในโหมดนี้ เพราะ CheckSlips ไม่ได้ตรวจปลายทางให้ร้าน ระบบร้านต้องตรวจผู้รับกับปลายทางที่บันทึกไว้เองอย่างเพียงพอ ตรวจยอดและช่วงเวลาโอน ผูก verification_id / remark กับออเดอร์ และกันใช้เลขอ้างอิงซ้ำ หากข้อมูลบัญชีถูกปิดจนยืนยันไม่พอ ให้ตรวจด้วยคนหรือเปลี่ยนใช้โหมด request
async function canAcceptSlipOnly(result, order, confirmMerchantReceiver) {
if (!result || !order || typeof confirmMerchantReceiver !== 'function') return false;
if (result.is_sandbox !== false || result.status !== 'verified' ||
result.authenticity_status !== 'verified' || result.bank_record_found !== true ||
result.is_duplicate !== false || result.receiver_validation_mode !== 'none') return false;
if (order.paymentStatus !== 'unpaid' || !order.expectedDestination ||
typeof order.id !== 'string' || !order.id || result.remark !== order.id ||
typeof order.verificationId !== 'string' || !order.verificationId ||
result.verification_id !== order.verificationId ||
typeof result.trans_ref !== 'string' || !result.trans_ref.trim()) return false;
if (!Array.isArray(result.flags) || result.flags.some(flag =>
typeof flag !== 'string' || flag.includes('DUPLICATE'))) return false;
if (typeof result.amount !== 'number' || !Number.isFinite(result.amount) || result.amount <= 0) return false;
const satang = Math.round(result.amount * 100);
if (!Number.isSafeInteger(order.amountSatang) || order.amountSatang <= 0 ||
!Number.isSafeInteger(satang) || Math.abs(result.amount * 100 - satang) >= 0.000001 ||
satang !== order.amountSatang) return false;
const transferred = Date.parse(result.transaction_date);
const opened = Date.parse(order.createdAt), expires = Date.parse(order.expiresAt);
if (![transferred, opened, expires].every(Number.isFinite) || expires < opened ||
transferred < opened - 60000 || transferred > expires) return false;
try {
return await confirmMerchantReceiver({ receiver: result.receiver,
receiverBankCode: result.receiver_bank_code, receiverType: result.receiver_type,
transactionReference: result.trans_ref, expectedDestination: order.expectedDestination }) === true;
} catch { return false; }
}
// Send receiver_validation_mode=none and remark=the stored order id.
// Save data.verification_id from POST 202 under the authenticated tenant.
// Read the final result from the authenticated API; never accept result JSON from a browser.
// confirmMerchantReceiver is YOUR server-owned recipient validation function.
// It must check the bank/payment rail and account against the order's trusted destination.
// If masking leaves insufficient evidence, return false for manual review.
// Do not substitute matching names or last four digits alone for destination proof.
// None-mode payment_assessment.ready=false is expected: CheckSlips did not check your destination.
// Before credit, in ONE database transaction:
// Lock the order; require unpaid; rerun this guard against its stored binding.
// Claim UNIQUE (tenant_id, verification_id) AND UNIQUE (tenant_id, trans_ref).
// Record the order, receipt, and credit/ledger once; commit all or roll back all.
// Webhook and polling retries must return the existing outcome on a unique conflict.ทดสอบรับงาน ดึงผล และ Webhook ด้วย Sandbox
ผล Sandbox เป็นข้อมูลจำลอง is_sandbox: true เสมอ ห้ามใช้ยืนยันการชำระเงินจริง แม้ผ่านเงื่อนไขจำลองและ payment_assessment.ready: true
POST /v1/sandbox/verify แบบเดิมยังตอบทันที หากต้องการทดสอบรูปแบบรับงาน 202 ให้เพิ่ม async_mode=true แล้วดึงผลที่ GET /v1/sandbox/verify/{verification_id} ใช้ X-Tenant-ID แบบ UUID ของร้านเดิม ข้อมูลจำลองหมดอายุใน 15 นาทีและไม่เข้าไปในประวัติงานจริงหรือหักโควตาตรวจจริง
เลือก scenario: success, receiver_abbreviated, receiver_mismatch, amount_mismatch, duplicate, pending, noqr, lowquality หรือ provider_outage ดูรายการที่ GET /v1/sandbox/scenarios โดย pending ใช้ทดสอบการรอผลต่อ ส่วน success ใช้ยอดจำลอง 1,500 บาทและผู้รับจำลองที่กำหนดไว้
ตัวอย่าง Sandbox async และเหตุการณ์ Webhook ซ้ำ
// Synthetic test only. This does not call a bank or confirm a real payment.
const base = 'https://api.checkslips.com/v1/sandbox';
const headers = { 'X-Tenant-ID': process.env.CHECKSLIPS_TENANT_ID };
const pause = ms => new Promise(resolve => setTimeout(resolve, ms));
async function readResult(id, maxWaitMs = 60000) {
const started = Date.now();
const terminal = new Set(['verified', 'flagged', 'rejected', 'failed', 'duplicate']);
while (true) {
const response = await fetch(base + '/verify/' + encodeURIComponent(id), {
headers, signal: AbortSignal.timeout(15000)
});
if (!response.ok) throw new Error('GET failed: ' + response.status + '; keep id=' + id);
const body = await response.json();
if (!body.success || !body.data) throw new Error('Invalid response; keep id=' + id);
const result = body.data;
if (terminal.has(result.status)) return result;
if (!['pending', 'processing'].includes(result.status)) {
throw new Error('Unknown status; keep id=' + id);
}
if (Date.now() - started >= maxWaitMs) return { ...result, verification_id: id };
await pause(3000);
}
}
const acceptedResponse = await fetch(base + '/verify?async_mode=true&scenario=success', {
method: 'POST', headers
});
if (acceptedResponse.status !== 202) throw new Error('Sandbox was not accepted');
const accepted = await acceptedResponse.json();
const id = accepted.data?.verification_id;
if (!accepted.success || !id || accepted.data.is_sandbox !== true) throw new Error('Invalid Sandbox job');
// Save id before polling; readResult uses the same 3-second polling above.
const resultUrl = base + '/verify/' + encodeURIComponent(id);
// A success fixture completes after about 2 seconds; pending stays processing.
// State expires after 15 minutes. Keep GET polling separate from POST submission.
const result = await readResult(id);
if (result.is_sandbox !== true) throw new Error('Expected synthetic evidence');
if (['pending', 'processing'].includes(result.status)) {
console.log('Still processing; resume GET later with id:', id);
} else {
// After completion, retrieve a signed, deterministic local webhook fixture:
const response = await fetch(resultUrl + '/webhook', { headers });
if (!response.ok) throw new Error('Fixture is not ready');
const fixture = await response.json();
// Feed fixture.raw_payload + fixture.headers to your LOCAL webhook handler twice.
// Use fixture.signing_secret only in the isolated Sandbox test configuration.
// Both deliveries have the same event_id; your handler must record ONE outcome.
// delivery_sent is false: CheckSlips has not sent any callback.
console.log('Synthetic fixture only:', fixture.payload?.event_id);
}
// Never use this public test secret or is_sandbox:true evidence in production.หลังผลเสร็จ GET /v1/sandbox/verify/{verification_id}/webhook ให้ raw_payload, headers และ signing_secret สำหรับส่งเข้าตัวทดสอบภายในของคุณเอง ดึงซ้ำจะได้ event_id เดิม ระบบไม่ได้ส่ง callback ออกไปจริง (delivery_sent: false) ก่อนผลเสร็จ endpoint นี้ตอบ 409 ส่วนรายการหมดอายุหรือของร้านอื่นตอบ 404
แยก signing secret สำหรับ Sandbox ออกจาก production และทดสอบส่ง fixture เดิมสองครั้งให้ระบบบันทึกผลครั้งเดียว จากนั้นตรวจว่าตัวกรองชำระเงินจริงปฏิเสธผลจำลองทุกครั้ง
ข้อมูลที่ API ส่งกลับให้ร้าน
ผลอยู่ใน data และบางฟิลด์เป็น null เมื่อไม่ทราบหรือไม่ได้ร้องขอ อย่าแปลง null เป็นผลผ่าน ใช้ verification_id ผูกข้อมูลทุกครั้ง
- status / authenticity_status
- ผลรวมของคำขอ / ผลยืนยันสลิปแยกจากเงื่อนไขผู้รับและยอด ใช้ verified เมื่อยืนยันครบ
- bank_record_found
- พบรายการโอนหรือไม่ true เพียงอย่างเดียวยังไม่ยืนยันสลิปครบ
- is_sandbox
- ต้องเป็น false อย่างชัดเจนก่อนรับชำระเงินจริง ผลจำลองเป็น true เสมอ แม้ payment_assessment.ready จะเป็น true
- payment_assessment
- ความพร้อมตามเงื่อนไขตรวจ: status, ready, summary และ requirements_missing ยังต้องผูกออเดอร์และบันทึกเครดิตครั้งเดียวในระบบร้าน
- receiver_assessment
- แยก image_match จาก provider_match; status: confirmed / unconfirmed / not_checked / not_configured พร้อม mode, source, basis และ summary ภาพตรงเพียงอย่างเดียวยังไม่ยืนยันเงินเข้าร้าน
- result_summary
- เหตุผลหลักและสิ่งที่ควรทำต่อ: code, title, message, next_action, retryable การ retry ใช้ GET รหัสเดิมเมื่อยังรอผล ไม่ใช่อัปโหลดสลิปเดิมใหม่
- processing_stages
- ขั้นตอน received / image / bank / receiver / amount / completed พร้อมสถานะตามหลักฐานที่บันทึกไว้ ไม่ใช่เวลาหรือประวัติการทำงานที่สร้างย้อนหลัง
- amount / transaction_date / trans_ref
- ยอด เวลาโอน และเลขอ้างอิง ใช้เวลาโอนพร้อมเขตเวลา อย่าสับสนกับเวลาที่ตรวจ
- sender_name / receiver_name
- ชื่อผู้โอนและผู้รับที่อ่านได้ บางข้อมูลอาจเป็น null หรือถูกปิดบางส่วน
- sender_bank_code / receiver_bank_code
- รหัสธนาคารผู้โอนและผู้รับ อ่านจากฟิลด์นี้ ไม่อาศัย sender.bank หรือ receiver.bank ที่อาจไม่มีข้อมูล
- sender / receiver
- ข้อมูลบัญชีผู้โอนและผู้รับแบบโครงสร้าง account.name และ proxy_type ข้อมูลอาจไม่ครบหรือถูกปิดบางส่วน
- expected_amount / amount_matches_expected
- ยอดที่ร้องขอให้เทียบ / ผลเทียบยอด หากไม่ส่ง match_amount อย่าใช้ null แทนการตรงยอด
- receiver_validation_mode / receiver_match
- โหมดที่ใช้จริง / ผลเทียบผู้รับ true, false หรือ null ไม่ใช้ receiver_match เพียงอย่างเดียวอนุมัติรายการ
- receiver_match_basis / receiver_match_source
- เกณฑ์และแหล่งหลักฐานที่ใช้เทียบผู้รับ
- expected_receiver_commitment
- ข้อมูลผูกคำขอกับผู้รับที่คาดหวังในโหมด request ใช้เทียบกับคำขอเดิม
- is_duplicate / flags
- เคยใช้รายการนี้หรือไม่ / รหัสเหตุผลประกอบผลตรวจ
- verification_evidence / confidence_score
- หลักฐานประกอบและคะแนน ไม่ใช่คำยืนยันเงินเข้าร้าน
- remark / processing_time_ms
- หมายเหตุที่ส่งมา / เวลาประมวลผล
- fee / merchant_id / receiver_type
- รายละเอียดเสริมเมื่อมีข้อมูล อาจเป็น null
ชื่อ เวลา ยอด และข้อมูลผู้รับเป็นข้อมูลส่วนบุคคล ส่งต่อและบันทึกเฉพาะที่ระบบร้านจำเป็นต้องใช้ ตรวจชนิดและ null ก่อนอ่านฟิลด์ซ้อนกัน
การรอผล สลิปซ้ำ และการส่งพร้อมกัน
- ถ้าได้ verification_id แล้ว ให้ดึงผลด้วยรหัสนั้น ห้าม POST ซ้ำเพียงเพราะครบ 30 หรือ 60 วินาที
- ถ้า GET ล้มเหลวชั่วคราว ให้พักแล้วดึงรหัสเดิมต่อ อย่าตีความการเชื่อมต่อขาดเป็นสลิปไม่ผ่าน
- duplicate เป็นผลสุดท้าย แต่ผลของภาพที่ส่งพร้อมกันอาจยังเป็น processing ขณะรายการต้นฉบับกำลังตรวจ ให้รอ GET ต่อจนเสร็จ
- เมื่อ duplicate ห้ามยืนยันยอดหรือออเดอร์ซ้ำ แม้ผลส่งกลับมีข้อมูลของสลิปต้นฉบับครบ
- ถ้า POST ขาดการเชื่อมต่อก่อนรับรหัส อย่าส่งซ้ำทันที ตรวจ ประวัติ หรือ GET /v1/history โดยเทียบ remark และเวลา หากยืนยันว่าไม่มีรายการรับงานจึงลองใหม่
- การลดคำขอซ้ำช่วยลดงานประมวลผล แต่โควตาลูกค้าและโควตาผู้ให้บริการเป็นคนละตัวนับ ดูยอดคงเหลือจริงจากหน้าลูกค้า
รับแจ้งเมื่อเสร็จด้วย Webhook (ทางเลือก)
- ตั้ง HTTPS URL ใน Webhooks และเก็บ signing secret บนเซิร์ฟเวอร์
- รับ raw body ตรวจ X-SlipVerify-Signature ซึ่งมีรูปแบบ sha256=<64 hex> โดยคำนวณ HMAC-SHA256 ของ raw body ด้วย signing secret
- หลังตรวจลายเซ็นแล้ว ใช้ event_id ป้องกันเหตุการณ์ซ้ำ บันทึกหรือเข้าคิวอย่างถาวรก่อนตอบ 2xx แล้วดึงรายละเอียดด้วย GET /v1/verify/{verification_id}
Webhook ส่ง event_type, event_id, timestamp และ data ที่มี verification_id, status, confidence_score, flags ไม่ได้ส่งชื่อและบัญชีทั้งหมด ระบบส่งซ้ำได้ หากปลายทางล้มเหลวต่อเนื่อง 20 เหตุการณ์จะปิดปลายทาง ให้แก้ระบบรับผล ลงทะเบียนปลายทางใหม่ และอัปเดต signing secret ใหม่ในระบบร้าน จากนั้นลบรายการที่ปิดไว้ ปุ่มทดสอบหรือหมุน secret ไม่ได้เปิดปลายทางเดิมกลับมา
ตัวอย่างตรวจลายเซ็นและจัดการ Webhook
ตัวอย่างต่อไปนี้เป็นฟังก์ชันตรวจลายเซ็น ให้เรียกจาก endpoint ของคุณก่อน parse JSON หรือเปลี่ยนข้อมูลออเดอร์ ยังต้องเพิ่มการบันทึกเหตุการณ์และงานเบื้องหลังของร้าน
import crypto from 'node:crypto';
function validSignature(rawBody, signature, secret) {
if (!secret || typeof signature !== 'string' || !/^sha256=[a-f0-9]{64}$/.test(signature)) return false;
const supplied = Buffer.from(signature.slice(7), 'hex');
const expected = crypto.createHmac('sha256', secret).update(rawBody).digest();
return supplied.length === expected.length && crypto.timingSafeEqual(supplied, expected);
}
// For Express: use express.raw({ type: 'application/json' }) BEFORE express.json().
// validSignature(req.body, req.get('X-SlipVerify-Signature'), process.env.CHECKSLIPS_WEBHOOK_SECRET)
// Reject false with 401. Parse req.body only after validation.
// Persist event.event_id + event.data to a durable queue/database before returning 2xx.
// Use event_id as a unique key; a repeated event must not confirm an order twice.Headers ที่ส่งจริง: X-SlipVerify-Signature, X-SlipVerify-Event, X-SlipVerify-Delivery และ X-SlipVerify-Timestamp โดย Delivery ตรงกับ event_id ใน body ใช้ event_id ที่ตรวจลายเซ็นแล้วเป็น unique key ในฐานข้อมูล
callback_url ที่ใส่ในคำขอเป็น callback แยกแบบ best effort ไม่มีลายเซ็นแบบ Webhook ที่ลงทะเบียน ห้ามถือว่าใช้สัญญาเดียวกัน แนะนำใช้ Webhook ที่ลงทะเบียน หรือดึงผลด้วย API ที่ยืนยันสิทธิ์แทน
อย่า parse แล้ว stringify ใหม่ก่อนคำนวณลายเซ็น และอย่าอัปเดตออเดอร์จากลายเซ็นที่ผ่านเพียงอย่างเดียว ต้องอ่านผลตรวจ ยอด ผู้รับ และความไม่ซ้ำด้วย
ทดสอบด้วย Sandbox ก่อนใช้สลิปจริง
GET /v1/sandbox/scenarios แสดงสถานการณ์จำลอง ส่งภาพไป POST /v1/sandbox/verify พร้อม X-Tenant-ID เพื่อจำลองผลตามชื่อไฟล์ เช่น duplicate_slip.jpg, fraud_slip.jpg, noqr_slip.jpg และ lowquality_slip.jpg
โหมดเริ่มต้นคืนข้อมูลจำลองทันที ส่วน async_mode=true ใช้ทดสอบรับ 202 และดึงผลตามตัวอย่างด้านบน ทั้งสองโหมดใช้แสดงผลและทดสอบการจัดการข้อผิดพลาด ไม่ใช้ยืนยันสลิปจริง และอย่าส่งชื่อไฟล์จำลองไป /v1/verify โดยหวังว่าจะเป็น Sandbox
ก่อนเปิดจริง ทดสอบการรอผล เหตุการณ์ซ้ำ ภาพอ่านไม่ได้ ยอดไม่ตรง ผู้รับไม่ตรง และ null ในข้อมูล รวมถึงการยืนยันออเดอร์ให้เกิดได้ครั้งเดียว
แก้ปัญหาจาก HTTP และรหัสเหตุผล
- 400 · DUPLICATE_SLIP
- ภาพสลิปนี้เคยตรวจแล้ว ดูรายการเดิมในประวัติ ห้ามยืนยันชำระซ้ำหรืออัปโหลดซ้ำเพื่อให้ผ่าน
- 409 · CONCURRENT_REQUEST
- ภาพเดียวกันกำลังประมวลผลจากคำขอก่อนหน้า ใช้รหัสรายการเดิมดึงผลต่อ หากยังไม่มีรหัสให้ดูประวัติและรอ ไม่ต้องส่งภาพซ้ำ
- 400 อื่น ๆ / 422 · ข้อมูลคำขอไม่ถูกต้อง
- ดู detail และ code ตรวจขนาดภาพ ชนิดไฟล์ ฟิลด์ และโหมดผู้รับ แก้ข้อมูลก่อนส่งใหม่
- 401 / 403 · สิทธิ์ไม่ผ่าน
- ตรวจ Tenant ID คีย์ที่เปิดใช้งาน และ IP ที่อนุญาต ห้ามส่งคีย์ให้ผู้ดูแลในแชต
- 404 · ไม่พบรหัสรายการ
- ตรวจ verification_id และ Tenant ID ให้ตรงร้าน
- 429 · จำกัดคำขอหรือโควตา
- อ่าน detail.code เพื่อแยกการจำกัดความถี่กับโควตาหมด พักคำขอหรือดูแพ็กเกจตามเหตุผล
- 5xx / ขาดการเชื่อมต่อ
- หากมีรหัสแล้วให้ดึงผลเดิมต่อ หากยังไม่มีรหัสให้ตรวจประวัติก่อนส่งภาพซ้ำ
ดูรหัส flags ในรายละเอียดผลตรวจ หรือดู ปัญหาที่พบบ่อย API Reference แสดง schema และ endpoint ทั้งหมด
เปิด API Referenceต้องการใช้ LINE โดยไม่เขียนโค้ด?
ใช้รหัสชั่วคราวจากเว็บเพื่อเชื่อมกลุ่มและยืนยันจากหน้าลูกค้า
ดูวิธีเชื่อม LINE