TokenPay DevelopersMerchant Dashboard

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 row

4. 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);
}