Skip to content

Quick start

This page takes you from getting your keys to your first received payment. The samples use the api() helper from Authentication and request signing.

  1. Sign in to the FIN console at https://portal.finixpay.app with the partner owner account.

  2. Open Account settings. The API keys section shows your API key (it starts with pk_live_, or is 32 hex characters for older accounts). Copy it.

  3. Click Create a new secret, confirm, and copy the secret that is shown. It is shown only once, and any previous secret stops working immediately.

  4. Store both values in your server’s environment variables or secret manager.

    Terminal window
    export PARTNER_API_KEY="pk_live_xxxxxxxxxxxxxxxxxxxxxxxx"
    export PARTNER_API_SECRET="<the secret you copied>"

Call GET /v1/account to check that your signing works, then GET /v1/banks to get the supported bank codes.

import { api } from './partner-api.mjs';
const { data: account } = await api('GET', '/v1/account');
console.log(account.status, account.balance.available);
const { data: banks } = await api('GET', '/v1/banks');
console.log(banks.map((b) => b.code)); // cache these codes

Example response of GET /v1/account:

{
"data": {
"object": "account",
"name": "Example Shop",
"status": "active",
"payinsEnabled": true,
"payoutsEnabled": true,
"ipAllowListEnabled": false,
"limits": {
"payinMinimum": "100.00",
"payinMaximum": "50000.00",
"payoutMinimum": "100.00",
"payoutMaximum": "50000.00",
"dailyWithdrawalLimit": "0.00"
},
"balance": { "currency": "THB", "total": "125000.00", "available": "120000.00" },
"createdAt": "2026-01-01T00:00:00.000Z"
},
"requestId": "req_Z3k9QmP2xR7tLw4NbV8cYh1s"
}

If you get a 401, see Common signing mistakes.

One call is enough: send the payer’s bank account inline in payer, together with the amount, your order number in reference and the URL that receives webhooks in webhookUrl. The payer (your end user’s bank account) is created the first time and reused automatically for later pay-ins and payouts with the same bankCode and accountNumber.

const { data: payin } = await api('POST', '/v1/payins', {
payer: {
bankCode: 'SCB',
accountNumber: '1234567890',
accountName: 'สมชาย สบายดี',
},
amount: '150.00',
currency: 'THB',
reference: 'order-10001',
webhookUrl: 'https://shop.example.com/webhooks/payments',
returnUrl: 'https://shop.example.com/orders/10001',
});
// save payin.id with the order (and payin.payerId with your user, if you like)

The important parts of the response:

{
"object": "payin",
"id": "pin_3f2b1c0d9e8f4a7b8c6d5e4f3a2b1c0d",
"reference": "order-10001",
"payerId": "payer_7c0a4b8e2f3d4a1b9c8d7e6f5a4b3c2d",
"requestedAmount": "150.00",
"amount": "150.37",
"currency": "THB",
"method": "promptpay_qr",
"status": "pending",
"checkoutUrl": "https://pay.example.com/3f2b1c0d-9e8f-4a7b-8c6d-5e4f3a2b1c0d",
"payTo": {
"bankCode": "KBANK",
"bankName": "Kasikorn Bank",
"accountNumber": "0987654321",
"accountName": "Example Co., Ltd.",
"promptpayId": "0812345678",
"qrPayload": "00020101021229370016A000000677010111011300668123456785802TH53037645406150.3763046572"
}
}

If the payer already existed under a different name, the stored name is kept (it is used to match the transfer) and the response has a warnings entry payer_name_differs. See Inline payer.

Choose one option:

  • Hosted payment page: redirect the payer to checkoutUrl. When they are done, the page sends them back to returnUrl.
  • Your own page: render a QR code from payTo.qrPayload with any QR library, and show the destination account from payTo. For bank_transfer pay-ins qrPayload is null; show the account only.
  • Ready-made image: create or retrieve the pay-in with ?include=paymentImage (for example POST /v1/payins?include=paymentImage, signed with that query string) to also get paymentImage, a PNG data URI for an <img src="...">.

When the transfer is matched, the platform sends a payin.succeeded event with POST to your webhookUrl. A minimal handler is below; see Webhooks for full signature verification.

// Express
app.post('/webhooks/payments', express.raw({ type: 'application/json' }), async (req, res) => {
if (!verifyWebhook(req.get('X-Webhook-Signature'), req.body)) return res.status(400).end();
const event = JSON.parse(req.body);
if (event.type === 'payin.succeeded') {
await markOrderPaid(event.data.reference, event.data.id); // must be idempotent
}
res.status(200).end(); // reply quickly, do slow work afterwards
});

Webhooks are not retried automatically. If your endpoint was down or you are unsure, call GET /v1/payins/{id} for the current status, or search by order number with GET /v1/payins?reference=order-10001.