Chargebee Plan Changes and Proration
Plan changes are where subscription billing gets messy
Signing a customer up is the easy part of a Chargebee integration. The work starts when someone upgrades mid-cycle, downgrades at renewal, adds seats, or changes their mind about a change they scheduled last week. Each of those touches three things that have to agree: what Chargebee charges, what the customer was told they would pay, and what your product lets them do.
This is how we approach Chargebee proration and plan changes in an integration: preview before charging, apply with explicit settings, and let product access follow the subscription state Chargebee reports.
The call and the settings that matter
In the current API, items on a subscription are changed with update_for_items on the subscription. Three groups of parameters decide what actually happens:
proratecontrols whether Chargebee creates prorated credits and charges for the part of the term already used. If you leave it out, the site-level setting applies. Pass it explicitly so the behaviour is visible in your code, not hidden in a dashboard setting someone may change later.invoice_immediatelydecides whether the resulting charges are invoiced now or held as unbilled charges for the next invoice. Again, the site setting applies if you omit it.change_optiondecides when the change takes effect:immediately,end_of_term, orspecific_datewith achanges_scheduled_attimestamp.
One parameter deserves its own warning: replace_items_list. It defaults to false, which means the items you send are added to the subscription's current list. A new plan still replaces the old one (unless your site has multi-plan subscriptions enabled), but addons you don't send stay on the subscription, including the ones the customer had on the old plan. A downgrade can quietly keep billing an addon from the plan the customer just left. Either send true to replace the whole item list, or remove the old addons explicitly; mandatory addons attached to the old plan have their own parameter, mandatory_items_to_remove, and a site setting that removes them on plan changes.
For per-unit addons there is also a per-item proration_type (full_term, partial_term, or none) that takes precedence over prorate for that item. Useful for seat-based pricing, and easy to overlook when the rest of the code only sets prorate.
Preview the charge before you apply it
Showing the exact amount before the customer confirms avoids a surprise on the next invoice. Chargebee has an estimate endpoint for exactly this: an estimate for updating a subscription for items, which takes the same kind of parameters (prorate, change_option, invoice_immediately) and returns the resulting subscription state and, if one would be generated, the immediate invoice.
The flow we recommend:
- The customer picks a new plan in your UI.
- Your backend calls the estimate with the same parameters it will use for the real change.
- The UI shows the amount due now and the next renewal amount.
- Only after the customer confirms does the backend call
update_for_itemswith identical parameters.
Building the parameters in one function used by both calls is what keeps the preview honest. If the estimate and the real change are assembled separately, they drift.
Scheduled changes and how to see them
Downgrades are often scheduled for the end of the term so the customer keeps what they paid for. A subscription with a pending change has has_scheduled_changes set to true, and retrieve_with_scheduled_changes returns the subscription with its future plan and billing period, while fields like status and next billing date keep their current values. Pending changes can be cancelled with remove_scheduled_changes.
Two cautions. First, Chargebee's documentation now marks scheduling through end_of_term and specific_date as deprecated in favour of its Ramps feature; check which mechanism your site uses before building new scheduling logic on the old parameters. Second, updating a subscription can interact with a pending scheduled change, so test "customer schedules a downgrade, then buys an addon" explicitly.
In the product, show scheduled changes plainly: "Your plan changes to Starter on 1 March". A customer who forgot about a downgrade they scheduled is a common source of "why did my plan change?" questions.
Webhooks: the order is not guaranteed
Chargebee sends events such as subscription_changed, subscription_changes_scheduled, subscription_cancelled, subscription_renewed, payment_succeeded, and payment_failed. The handling rules are the same as for any billing webhook, with a few Chargebee specifics:
- Authentication is HTTP basic auth on your endpoint, over HTTPS. Configure it; do not rely on an unguessable URL alone.
- Retries. A failed delivery is retried up to seven times over roughly three days, so a temporarily broken endpoint recovers on its own, provided your handler deduplicates.
- Deduplicate on the event
id, which Chargebee documents for exactly this purpose. - Order is not guaranteed. Each resource carries a
resource_versionthat increases with every change; ignore an event whose version is older than the one you have stored, or simply re-read the subscription from the API before acting.
As with any webhook, the handler should only authenticate, store, and enqueue. The worker re-reads the subscription and updates your entitlements table.
Access follows the subscription, not the click
Unlock the new plan's features when your worker has processed the change and recorded the new state, not the moment the customer clicks "upgrade". For an immediate upgrade that is seconds; for a scheduled downgrade it is the renewal date, which is exactly when access should change. The entitlements table and reconciliation job behind this are the same as for any billing provider; we describe them in our notes on Stripe webhooks.
Where this stops being right
- Very simple pricing. One plan, monthly, no addons: most of this is overkill. Proration settings and scheduled changes matter once customers move between plans.
- Usage-based components. Metered usage brings its own problems (late events, corrections, invoice timing) that a plan-change flow does not cover.
- Changes made in the Chargebee dashboard. Your finance team will change subscriptions by hand. The webhook and reconciliation path must handle those as well as changes made through your product, which is another reason access should follow recorded state rather than your own UI events.
FAQ
Should prorate be on or off? It is a pricing decision, not a technical one. What matters technically is passing it explicitly, so behaviour does not change when someone edits a site setting.
How do we show customers what they will pay before they confirm? Call the estimate for updating the subscription with the same parameters you will use for the change, and show its immediate invoice amount.
Why does a customer still pay for an addon after changing plans?
Usually replace_items_list was left at its default of false: the new plan replaced the old one, but the old plan's addons stayed on the subscription.
Chargebee is one of the billing platforms we have integrated, along with Stripe and Orb. If you are planning plan changes or a migration of your pricing, see payment and subscription integration.