Beta
Subscriptions
Beta — recurring bills, collected by QRIS every cycle.
Beta, closed. These endpoints answer 404 for every account except the few we have enabled by hand — so they will 404 for you today, and that is not a problem with your key. The shape is published now so you can build against it, and it may still change before general availability. The full reference is in /v1/openapi.json. Ask us if you want in.
Every cycle raises an invoice, and the customer authorises a QRIS payment for it. There is no card on file and nothing is charged automatically — a renewal happens because somebody scanned a code.
The shape of it
A customer is a person you bill. A plan is a price and a cycle. A subscription joins the two. Each cycle it raises an invoice, and each invoice gets a payment request you can point the customer at.
1. Set up a plan and a customer
curl __BASE__/v1/subscription_plans \
-H "Authorization: Bearer kp_test_..." \
-H "Content-Type: application/json" \
-d '{"code":"basic","name":"Bulanan","amount":150000,"interval_unit":"month"}'
curl __BASE__/v1/subscription_customers \
-H "Authorization: Bearer kp_test_..." \
-H "Content-Type: application/json" \
-d '{"name":"Budi","phone":"+628123456789","external_id":"crm-42"}'Send external_id if you have your own identifier. Posting the same one again updates that customer instead of creating a second — which is what makes re-running an import safe.
amount is required even when it is 0. A free plan is a real plan, so an absent field cannot be read as a free one.
2. Start the subscription
curl __BASE__/v1/subscriptions \
-H "Authorization: Bearer kp_test_..." \
-H "Content-Type: application/json" \
-d '{"customer_id":"cust_...","plan_id":"plan_..."}'The opening cycle is billed immediately, so the first invoice already exists when this returns. If the plan has a trial, nothing is raised until the trial ends — a trial that never converts never appears in your invoices at all.
3. Collect
Read the invoice, take its payment_request_id, and fetch that from GET /v1/transactions/{id} for the QRIS payload. The invoice outlives every attempt: a QRIS code expires in an hour, the invoice is due for days, and a fresh attempt replaces the dead one.
4. Grant access on paid_through, not on status
status is where the subscription sits in its lifecycle. paid_through is whether the customer may use your service right now. They answer different questions.
A past_due subscriber inside the period they already paid for still has access — that is what the grace period is for. Gate on status alone and you cut off paying customers the moment an invoice slips.
const ok = sub.paid_through && new Date() < new Date(sub.paid_through);5. Missed payments
An unpaid invoice past its grace moves the subscription to past_due and emits invoice.overdue. Access continues until paid_through lapses on its own. Paying late settles the invoice and extends access from there — nothing is lost.
6. Changes and cancellation
POST /v1/subscriptions/{id}/change takes effect at the next renewal, and only then. There is no immediate option: proration is not available in this release, and applying a paid upgrade before it is paid for would grant service nobody authorised. The scheduled values show as pending_plan_id and pending_quantity until the renewal applies them.
Cancelling with at_period_end: true honours the service already paid for. The default stops access now. Neither refunds anything.
Cancellation is final. A payment arriving afterwards settles its invoice — the debt was real — and grants no access and restarts no billing. A second cancel is 409 already_canceled, not an error in your request.
Webhooks
invoice.issued, invoice.paid, invoice.overdue, invoice.voided, and the subscription.* lifecycle events. Each body carries the same object this API returns for it, so what you store from a webhook and what you fetch from a GET are the same thing.
Delivery is at-least-once. Deduplicate on Kasera-Event-Id and treat events as possibly out of order — an event is a nudge to read the object, not a substitute for reading it.
Test mode
A kp_test_ key creates a sandbox subscription. It raises sandbox payment requests and sends no reminders to anybody, so a real phone number never hears from a subscription you were only trying out.
What is not here yet
Only monthly and shorter cycles are currently offered. Proration, coupon codes and partial payments are not in this release, and automatic debit does not exist — every cycle is authorised by the customer.