Payment requests
Create request
Create a payment request and get a checkout link.
/v1/transactionsHeaders
Optional, up to 255 bytes, and the only thing that deduplicates a create. Send none and every attempt is its own payment request. Resending the same key returns the original under 200 instead of 201. The limit is bytes, not characters, so a non-ASCII key runs out sooner. See Idempotency.
Example: order-1234
Body parameters
Whole rupiah. See the limits.
Example: 150000
Shown on the payment page. Max 255 characters.
Example: Community T-shirt
Your own order number. Stored, echoed back, and filterable on the list endpoint — it does not deduplicate: two creates naming the same external_id are two payment requests. Only Idempotency-Key deduplicates.
Example: ORD-1234
An array of method codes, the ones this payment may be paid with: one code goes straight to that method, several show the buyer a picker in the order you sent, and omitting it offers everything your account has enabled. A code that does not exist, or is not enabled on your account, is refused 422 payment_method_unavailable rather than dropped. `qris` and the Virtual Account codes exist today, and the PayLater codes are in private preview; e-wallets do not exist yet. See Payment methods. This field decides whether the response carries `payment`: name ONE code and the instrument — the QRIS string, the Virtual Account number — is minted on the create and comes back with it. Name several, or none, and the buyer has not chosen a rail yet, so nothing is minted and you get `checkout_url` alone until they pick. Note the asymmetry: you SEND `payment_methods`, you READ BACK `payment_method`.
Example: ["va_bca", "qris"]
Whether Kasera Pay emails the buyer a receipt when this payment succeeds. `true` sends it and `false` does not, whatever the merchant's setting says. Omit it and the merchant's receipt setting in Settings → Payment methods decides, which is off by default. A receipt is only sent for a live payment that carries a buyer email, in `customer.email` or typed on the checkout page.
Example: true
Everything that shapes the Kasera Pay Checkout page: `steps` plus `is_name_required`, `is_email_required` and `is_phone_required`. Sending it is what makes a create a Checkout create — the buyer walks `checkout_url` and the page collects what is missing. Omit it and the create is Direct API: nothing walks a page, so a method that needs a field you did not send is refused 422 customer_required. Omitting it also inherits the three asks from the merchant's own payment-method settings; sending the object replaces all three at once, so an object carrying only `steps` turns those account defaults off for that payment.
The checkout flow, as an array of step names, walked in the order you send: `customer` (the details form), `payment_method` (the picker), `payment` (the QR, code or redirect). Default is all three. Drop `customer` when you already hold the buyer's details, drop `payment_method` when `payment_methods` names a single code, and reorder freely — `payment_method` first is a valid flow. `payment` is always last and is appended if you leave it out. Names rather than numbers: the array order already says what runs when, so a number could only ever disagree with it.
Example: ["customer", "payment_method", "payment"]
Ask the buyer for their name on the `customer` step. A method that needs the name requires it anyway, so this is for when you want it and no method forces it. Defaults to the merchant's own payment-method setting — but only when you omit the whole `checkout` object, since sending it replaces all three.
Example: true
Ask the buyer for their email on the `customer` step. If the receipt email is on — `send_receipt: true` on this create, or the merchant's switch in Settings → Payment methods (off by default) — a live payment that succeeds then sends the buyer an email receipt at that address; test payments send none. Defaults to the merchant's own payment-method setting when you omit the whole `checkout` object.
Example: true
Ask the buyer for their phone on the `customer` step, in E.164 (+628...). Defaults to the merchant's own payment-method setting when you omit the whole `checkout` object.
Example: true
Optional name, email and phone (E.164). Predates customer and is kept because it is stored and echoed — on this API, send customer instead. Only customer satisfies a payment method's requirements; a name sent here will not fill a Virtual Account.
Your own reference, max 64 characters, echoed back and filterable on the list endpoint. It does not deduplicate — retrying with the same merchant_ref creates a second payment request. Send an Idempotency-Key if you want retry protection.
Example: INV-2026-001
Optional name (max 120), email and phone of the paying customer. Echoed in responses and the payment.paid webhook; the name shows on the payment page. This is also what satisfies a payment method's requirements — a Virtual Account needs customer.name. On a Virtual Account the 120 characters are what we store, not what the bank receives: that copy is transliterated to ASCII and cut to 30 — see Virtual Account. Omit it on Checkout and the page asks the buyer; omit it on Direct API and a method that needs it is refused 422. See Payment methods.
Up to 50 lines of {name, price, quantity}, shown on the payment page. The sum of price×quantity must equal amount, or the request is rejected 422 — amount stays authoritative.
https URL, max 2048 characters. A test key also accepts http, so a store on localhost can develop the full redirect flow; live creates are https only. After a successful payment the checkout page shows a back-to-store button and redirects there with ?id=payreq_...&status=succeeded appended.
Example: https://toko.example/selesai
Default 60, maximum follows the account configuration.
Example: 60
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,
"description": "Community T-shirt",
"external_id": "ORD-1234",
"merchant_ref": "INV-2026-001",
"customer": { "name": "Budi", "email": "budi@toko.dev" },
"order_items": [
{ "name": "Community T-shirt", "price": 75000, "quantity": 2 }
],
"return_url": "https://toko.example/selesai",
"payment_methods": ["qris"]
}'{
"id": "payreq_9b2f...",
"livemode": true,
"status": "pending",
"currency": "IDR",
"amount": 150000,
"fee": 1300,
"net": 148700,
"description": "Community T-shirt",
"external_id": "ORD-1234",
"merchant_ref": "INV-2026-001",
"customer": { "name": "Budi", "email": "budi@toko.dev" },
"order_items": [
{ "name": "Community T-shirt", "price": 75000, "quantity": 2 }
],
"return_url": "https://toko.example/selesai",
"checkout_url": "https://pay.kasera.id/p/xK3f...",
"source": "api",
"payment_method": "qris",
"payment": {
"type": "qr",
"qr_string": "00020101021226670016COM.KASERA.WWW...6304A1B2"
},
"instructions": {
"title": "Cara membayar dengan QRIS",
"steps": [
"Buka aplikasi e-wallet atau mobile banking.",
"Pilih menu bayar dengan QRIS, lalu scan kode QR di halaman pembayaran.",
"Periksa nama merchant dan nominal, lalu konfirmasi pembayaran.",
"Pembayaran terkonfirmasi otomatis dalam beberapa detik."
]
},
"expires_at": "2026-08-11T13:00:00+07:00",
"paid_at": null,
"created_at": "2026-08-11T12:00:00+07:00"
}