Payment requests

Create request

Create a payment request and get a checkout link.

POST/v1/transactions

Headers

Idempotency-Keyheaderoptional

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

amountintegerrequired

Whole rupiah. See the limits.

Example: 150000

descriptionstringoptional

Shown on the payment page. Max 255 characters.

Example: Kaos komunitas

external_idstringoptional

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

payment_methodsarrayoptional

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`, the Virtual Account codes and `card` exist today; e-wallets and paylater do not. 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"]

checkoutobjectoptional

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.

checkout.stepsarrayoptional

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"]

checkout.is_name_requiredbooleanoptional

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

checkout.is_email_requiredbooleanoptional

Ask the buyer for their email on the `customer` step. Worth setting if you send your own receipts; a card asks for it regardless. Defaults to the merchant's own payment-method setting when you omit the whole `checkout` object.

Example: true

checkout.is_phone_requiredbooleanoptional

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

payerobjectoptional

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.

merchant_refstringoptional

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

customerobjectoptional

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, a card needs customer.email. 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.

order_itemsarrayoptional

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.

return_urlstringoptional

https URL, max 2048 characters. 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

expires_in_minutesintegeroptional

Default 60, maximum follows the account configuration.

Example: 60

POST /v1/transactions
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": "Kaos komunitas",
    "external_id": "ORD-1234",
    "merchant_ref": "INV-2026-001",
    "customer": { "name": "Budi", "email": "budi@toko.dev" },
    "order_items": [
      { "name": "Kaos komunitas", "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": "Kaos komunitas",
  "external_id": "ORD-1234",
  "merchant_ref": "INV-2026-001",
  "customer": { "name": "Budi", "email": "budi@toko.dev" },
  "order_items": [
    { "name": "Kaos komunitas", "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 Anda.",
      "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"
}