Examples
Copy-pasteable end-to-end integrations for the four flows merchants integrate first. Each uses the sandbox — replace the tk_test_ placeholder with your own sandbox key.
1. Accept a payment
Create a payment and redirect the buyer to the hosted checkout URL returned in data.checkout_url.
// Node 20+ (global fetch)
const res = await fetch('https://api.tokenpayment.io/v1/payments', {
method: 'POST',
headers: {
'X-API-Key': process.env.TOKENPAY_API_KEY, // tk_test_...
'Content-Type': 'application/json',
},
body: JSON.stringify({
order_id: 'order_' + Date.now(),
amount: 1000, // minor units (satang for THB)
currency: 'THB',
payment_method: 'promptpay',
return_url: 'https://your-site.example/return',
}),
});
const { data } = await res.json();
console.log('redirect buyer to', data.checkout_url);2. Verify a webhook
TokenPay signs every webhook with an HMAC-SHA256 of the raw request body using your webhook secret. Verify with a constant-time compare before acting on the event.
// Node 20+
import crypto from 'node:crypto';
export function verifyTokenpaySignature(rawBody, headerSig, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('hex');
const a = Buffer.from(expected, 'utf8');
const b = Buffer.from(headerSig, 'utf8');
if (a.length !== b.length) return false;
return crypto.timingSafeEqual(a, b);
}
// Express handler — note the express.raw() body parser so you keep the
// exact bytes TokenPay signed.
app.post('/webhooks/tokenpay', express.raw({ type: 'application/json' }), (req, res) => {
const ok = verifyTokenpaySignature(
req.body,
req.header('X-Tokenpay-Signature'),
process.env.TOKENPAY_WEBHOOK_SECRET,
);
if (!ok) return res.status(401).send('bad signature');
const event = JSON.parse(req.body.toString('utf8'));
console.log('event', event.type, event.data);
res.status(200).send('ok');
});3. Simulate a failed payment
Use the /v1/sandbox/payments/simulate endpoint to drive a test payment into failed without any real funds movement. Only tk_test_ keys may call this endpoint — live keys get a 403 SANDBOX_ONLY.
curl -X POST 'https://api.tokenpayment.io/v1/sandbox/payments/simulate' \
-H 'X-API-Key: tk_test_your_sandbox_key' \
-H 'Content-Type: application/json' \
-d '{"scenario":"matched_then_failed"}'
# Supported scenarios:
# matched_then_completed → payment succeeds end-to-end
# matched_then_failed → matched, then the on-chain tx fails
# expired → payment window expires without a match
# settlement_cycle → a fully-settled payment with a payout row4. List recent payments
Fetch the most recent payments for reconciliation. The list endpoint paginates with limit + cursor; iterate until has_more is false.
// Node 20+
async function* iteratePayments() {
let cursor = null;
do {
const u = new URL('https://api.tokenpayment.io/v1/payments');
u.searchParams.set('limit', '50');
if (cursor) u.searchParams.set('cursor', cursor);
const res = await fetch(u, {
headers: { 'X-API-Key': process.env.TOKENPAY_API_KEY },
});
const { data, has_more, next_cursor } = await res.json();
for (const p of data) yield p;
cursor = has_more ? next_cursor : null;
} while (cursor);
}
for await (const payment of iteratePayments()) {
console.log(payment.id, payment.status, payment.amount);
}