Skip to content

Slip verification

Two endpoints deal with slips:

EndpointUse it whenEffect
POST /v1/slips/verifyYou want to know whether a slip is genuine and what it containsChanges no payment
POST /v1/payins/{id}/slipThe payer has paid but the pay-in was not matched yet, and you want to confirm it with the slipIf it matches, the pay-in becomes succeeded and a webhook is sent
POST /v1/slips/verify

Two request forms:

  • JSON: send the text of the slip’s QR code in payload (max 2000 characters). Optionally add checkDuplicate: true to check whether the slip was used before.
  • multipart/form-data: send the slip image (JPG or PNG, max 5 MB) as the only part, named file. Sign the hash of the file bytes; see multipart requests. (checkDuplicate is not available in this form.)
import { readFile } from 'node:fs/promises';
import { api, signRequest } from './partner-api.mjs';
// 1) QR payload
const { data: byPayload } = await api('POST', '/v1/slips/verify', {
payload: qrText,
checkDuplicate: true,
});
// 2) Image: sign the file bytes, send only the `file` part
const bytes = await readFile('slip.jpg');
const form = new FormData();
form.append('file', new Blob([bytes], { type: 'image/jpeg' }), 'slip.jpg');
const res = await fetch('https://api.example.com/v1/slips/verify', {
method: 'POST',
headers: signRequest('POST', '/v1/slips/verify', bytes),
body: form,
});
const { data: byImage } = await res.json();

A failed check still returns HTTP 200. Look at verified and failureCode.

{
"data": {
"object": "slip_verification",
"verified": true,
"failureCode": null,
"payload": "0041000600000101030040220...",
"bankReference": "202601011234567890",
"transferredAt": "2026-01-01T00:04:12.000Z",
"amount": "150.37",
"currency": "THB",
"sender": {
"bankCode": "SCB",
"bankName": "Siam Commercial Bank",
"accountNumber": "xxx-x-x4567-x",
"accountName": "นาย สมชาย ส",
"accountNameEn": "MR. SOMCHAI S"
},
"receiver": {
"bankCode": "KBANK",
"bankName": "Kasikorn Bank",
"accountNumber": "xxx-x-x5432-x",
"accountName": "บจก. ตัวอย่าง",
"accountNameEn": "EXAMPLE CO., LTD."
}
},
"requestId": "req_..."
}
failureCodeMeaning
slip_not_foundNo transfer matches the slip
duplicate_slipThe slip was used before
amount_mismatchThe amount does not match
receiver_mismatchThe receiver does not match
invalid_imageThe image could not be read or the file is invalid
invalid_payloadThe payload is invalid
verification_unavailableTemporarily unavailable; try again later

sender and receiver come from the bank. Some fields may be null or partly masked.

With a test key (pk_test_...) the slip is not checked with the bank and no quota is used. Results are simulated from the payload:

payload starts withResult
TESTFAILverified: false, failureCode: "slip_not_found"
TESTDUPverified: false, failureCode: "duplicate_slip"
anything else (or an image)verified: true, amount 100.00 THB, bankReference TEST-REF-0000000000
HTTPcodeMeaning
400parameter_missingNeither payload (JSON) nor file (multipart) was sent
400file_invalidNo file, not a JPG/PNG, or larger than 5 MB
400parameter_unknownThe multipart body has parts other than file
422slip_verification_unavailable, slip_verification_failedThe verification service has a problem; try again later
429quota_exceededYour slip verification quota is used up; contact the team
POST /v1/payins/{id}/slip

Use it when the payer has transferred but the pay-in is still pending, for example when the payer sends you the slip. Send multipart/form-data with a single file part (JPG or PNG, max 5 MB) and sign the hash of the file bytes. See uploadSlip in the code samples.

{
"data": {
"object": "slip_upload",
"matched": true,
"slipStatus": "matched",
"payin": { "object": "payin", "id": "pin_3f2b1c0d9e8f4a7b8c6d5e4f3a2b1c0d", "status": "succeeded", "...": "..." }
},
"requestId": "req_..."
}
  • matched: true: the slip matches and the pay-in is now succeeded. The usual payin.succeeded webhook is sent.
  • matched: false: not matched yet; check slipStatus and the pay-in’s status.
HTTPcodeMeaning
400file_invalidNo file, not a JPG/PNG, or larger than 5 MB
404resource_not_foundUnknown pay-in
409payin_not_pendingThe pay-in is no longer waiting for payment (succeeded, expired or canceled)
409slip_duplicateThe slip was already used
422slip_unreadableThe QR code on the slip could not be read; ask for a clearer image
422slip_amount_mismatchThe slip amount differs from the pay-in’s amount
422slip_receiver_mismatchThe receiving account differs from payTo
422slip_verification_failedThe bank check failed
422slip_verification_unavailableSlip verification is temporarily off