Usage-based subscription is a type of recurring payment model where the payment amount varies and is determined based on the user's consumption or usage at a specific cut-off time before the next billing cycle. This model offers flexibility for businesses that charge customers based on actual usage rather than a fixed fee.
Common use cases include utility services, postpaid plan for telecommunication, ride hailing, SaaS or cloud storage subscriptions, and many more.
How to integrate
In usage-based subscriptions, the amount to be charged must be defined for each billing cycle before the cycle begins. This ensures that the end user’s balance can be accurately deducted based on their usage during the period.
First create the Payment Sessions with subscription type to initate the linking
Request - POST /sessions
{ "reference_id": "TEST_{{$timestamp}}", "customer": { "reference_id": "test-{{$timestamp}}", "type": "INDIVIDUAL", "individual_detail": { "given_names": "TEST" } }, "session_type": "SUBSCRIPTION", "subscription": { "schedule": { "interval": "MONTH", "interval_count": 1, "anchor_date": "2026-04-09T23:23:52+07:00", "total_recurrence": 100, "retry_interval": "DAY", "retry_interval_count": 5, "total_retry": 7, "failed_attempt_notifications": [ 1, 3, 5 ] }, "immediate_payment": false, "failed_cycle_action": "RESUME" }, "currency": "IDR", "amount": 10000, "mode": "PAYMENT_LINK", "country": "ID", "locale": "en", "description": "Insurance Plan Registration", "success_return_url": "https://xendit.co/success", "cancel_return_url": "https://xendit.co/failure" }Response - POST /sessions
{ "payment_session_id": "ps-69d5f625da22a3584993b763", "created": "2026-04-08T06:31:02.078Z", "updated": "2026-04-08T06:31:02.078Z", "status": "ACTIVE", "reference_id": "TEST_1775629861", "currency": "IDR", "amount": 10000, "country": "ID", "expires_at": "2026-04-08T07:01:01.083Z", "session_type": "SUBSCRIPTION", "mode": "PAYMENT_LINK", "locale": "en", "business_id": "5f1e60a0abb3a70ffd45e485", "customer_id": "cust-da1a36cb-e33d-4f62-a34b-256029b5d9c5", "description": "Insurance Plan Registration", "subscription": { "schedule": { "interval": "MONTH", "interval_count": 1, "anchor_date": "2026-04-09T23:23:52+07:00", "total_recurrence": 100, "retry_interval": "DAY", "retry_interval_count": 5, "total_retry": 7, "failed_attempt_notifications": [ 1, 3, 5 ] }, "immediate_payment": false, "failed_cycle_action": "RESUME" }, "success_return_url": "https://xendit.co/success", "cancel_return_url": "https://xendit.co/failure", "recurring_plan_id": "repl_8eb75eae-30eb-40d9-a420-423e88b703e3", "payment_link_url": "https://dev.xen.to/a5LftpcH" }After creating a session, redirect the end user to the Xendit-hosted page using the
payment_link_urlobject or render for component mode from the API response. This step allows the end user to link their payment method to the subscription plan.Once the end user successfully links their payment method, Xendit will send a
recurring.plan.activationwebhook to confirm the activation of the subscription plan.Xendit will send a
recurring.cycle.createdwebhook for each upcoming billing cycle, notifying you that the next cycle is about to begin. You can store the cycle.id on your end to be used for updating next cycle billing (learn more).[Important] To deduct the user's balance accurately for the upcoming billing cycle, you must use the Update Cycle API to define the total usage-based amount to be charged. This update must be completed before the
scheduled_timestampdate (e.g., ifscheduled_timestampis "2026-12-20," the update should occur no later than "2026-12-19"). Once the date component of thescheduled_timestampis reached, no further updates can be made to that cycle.This process needs to be repeated for each subsequent billing cycle to reflect the correct usage-based charges.
To start, you need to create a customer object in Xendit by registering the end user's details.
Request - POST /customers
{ "reference_id": "demo_1475801962688", "type": "INDIVIDUAL", "individual_detail": { "given_names": "John", "surname": "Doe" }, "email": "customer@website.com", "mobile_number": "+628121234567890" }Response - POST /customers
{ "type": "INDIVIDUAL", "date_of_registration": null, "email": "customer@website.com", "mobile_number": "+628121234567890", "phone_number": null, "created": "2024-12-06T02:42:36.343Z", "updated": "2024-12-06T02:42:36.343Z", "description": null, "hashed_phone_number": null, "domicile_of_registration": null, "kyc_documents": [], "id": "cust-ab93b98b-3e79-4961-b243-66a0e66daadc", "reference_id": "demo_1475801962688", "metadata": null, "individual_detail": { "given_names": "John", "given_names_non_roman": null, "surname": "Doe", "surname_non_roman": null, "nationality": null, "date_of_birth": null, "place_of_birth": null, "gender": null, "employment": null }, "business_detail": null, "addresses": [], "identity_accounts": [] }Once the end user is registered, create a subscription plan tailored to their preference and needs
Request - POST /recurring/plans
{ "reference_id": "plan-{{$timestamp}}", "customer_id": "cust-ab93b98b-3e79-4961-b243-66a0e66daadc", "currency": "IDR", "amount": 10000, "payment_tokens": [ { "payment_token_id": "pt-7f570a37-4e1f-4952-85d5-606b59428349", "rank": 1 } ], "schedule": { "interval": "MONTH", "interval_count": 1, "anchor_date": "2026-04-09T23:23:52+07:00", "total_recurrence": 100, "retry_interval": "DAY", "retry_interval_count": 5, "total_retry": 7, "failed_attempt_notifications": [ 1, 3, 5 ] }, "immediate_payment": false, "failed_cycle_action": "RESUME", "notification_channels": [ "EMAIL", "EMAIL" ], "locale": "en", "payment_link_for_failed_attempt": true, "metadata": { "customKey": "customValue" }, "description": "Subscription Example 01" }Response - POST /recurring/plans
{ "id": "repl_19299f7e-aeb1-4006-a449-645121965efe", "reference_id": "plan-1775629572", "customer_id": "cust-ab93b98b-3e79-4961-b243-66a0e66daadc", "failed_cycle_action": "RESUME", "recurring_cycle_count": 0, "country": "ID", "currency": "IDR", "amount": 10000, "status": "ACTIVE", "created": "2026-04-08T06:26:12.448Z", "updated": "2026-04-08T06:26:12.448Z", "payment_tokens": [ { "payment_token_id": "pt-7f570a37-4e1f-4952-85d5-606b59428349", "rank": 1 } ], "schedule": { "interval": "MONTH", "interval_count": 1, "total_recurrence": 100, "anchor_date": "2026-04-09T23:23:52+07:00", "retry_interval": "DAY", "retry_interval_count": 5, "total_retry": 7, "failed_attempt_notifications": [ 1, 3, 5 ], "created": "2026-04-08T06:26:12.448Z", "updated": "2026-04-08T06:26:12.448Z" }, "immediate_payment": false, "locale": "en", "notification_channels": [ "EMAIL", "EMAIL" ], "metadata": { "customKey": "customValue" }, "description": "Subscription Example 01", "items": null, "payment_link_for_failed_attempt": true, "failure_code": null }After creating a subscription, redirect the end user to the Xendit-hosted page using the
payment_link_urlobject from the API response. This step allows the end user to link their payment method to the subscription plan.Once the end user successfully links their payment method, Xendit will send a
recurring.plan.activationwebhook to confirm the activation of the subscription plan.Xendit will send a
recurring.cycle.createdwebhook for each upcoming billing cycle, notifying you that the next cycle is about to begin. You can store the cycle.id on your end to be used for updating next cycle billing.[Important] To deduct the user's balance accurately for the upcoming billing cycle, you must use the Update Cycle API to define the total usage-based amount to be charged. This update must be completed before the
scheduled_timestampdate (e.g., ifscheduled_timestampis "2026-12-20," the update should occur no later than "2026-12-19"). Once the date component of thescheduled_timestampis reached, no further updates can be made to that cycle.This process needs to be repeated for each subsequent billing cycle to reflect the correct usage-based charges.
Learn more about payments lifecyle here.