Direct API

Virtual Account

A per-transaction account number the buyer transfers to from their bank.

The buyer gets a number, opens their banking app, and transfers to it. No app switching, no QR — the method Indonesians reach for when the amount is large or the phone is not the one holding the money. Every bank is the same call; only the code changes.

Integration steps

  1. Create the payment request.
  2. Display the payment code and the instructions on your page.
  3. Acknowledge the payment — the payment.paid webhook.
  4. Check status when you need certainty: GET /v1/transactions/:id.

Bank codes

CodeBank
va_bcaBCA
va_briBRI
va_bniBNI
va_mandiriMandiri
va_permataPermata
va_cimbCIMB Niaga
va_danamonDanamon
va_maybankMaybank

GET /v1/payment_methods returns the banks your account can use. Read it rather than hard-coding this table — a bank can be enabled or suspended without a release on your side.

1. Create the payment request

One code goes straight to that bank. Send several and the buyer chooses; omit payment_methods and every method your account has enabled is offered. A Virtual Account needs the payer's name: send customer.name, or the create is refused 422 customer_required.

curl https://pay.kasera.id/v1/transactions \
  -H "Authorization: Bearer kp_live_..." \
  -H "Idempotency-Key: order-1234" \
  -H "Content-Type: application/json" \
  -d '{
    "amount": 150000,
    "external_id": "ORD-1234",
    "payment_methods": ["va_mandiri"],
    "customer": { "name": "Budi" }
  }'
{
  "id": "payreq_9b2f...",
  "status": "pending",
  "payment_method": "va_mandiri",
  "payment": {
    "type": "payment_code",
    "payment_code": "8890812345678901",
    "bank": "Mandiri",
    "display_name": "Mandiri Virtual Account"
  },
  "instructions": { "title": "Cara membayar via Mandiri Virtual Account", "steps": ["..."] },
  "expires_at": "2026-08-28T12:00:00+07:00"
}

2. Display the payment code

Show payment.payment_code as large selectable text with a copy button, name the bank from payment.display_name, and print the instructions block underneath. Buyers mistype these numbers; a copy button removes most support tickets.

3. Acknowledge the payment

The payment.paid webhook fires when the transfer clears, which may be long after you created the request.

4. Check status

GET /v1/transactions/:id — worth polling on a slow schedule for VA specifically, because the gap between create and payment is measured in hours.

Good to know

Virtual Account
ExpirySame rule as every method: 60 minutes by default, up to 24 hours via expires_in_minutes
ConfirmationWhen the transfer clears, not when you created it
RefundableNo — VA cannot be reversed

A VA sits unpaid far longer than a QR. Do not hold stock or seats on pending alone, and fulfil only on the webhook — a buyer who transfers in the last hour of the deadline is normal, not an anomaly.