A Chargebee subscription moves through defined states across its lifecycle. Understanding these transitions is essential for building reliable integrations — the wrong assumption about subscription state is one of the most common sources of integration bugs.
┌─────────┐
│ future │ (start_date in the future)
└────┬────┘
│ start_date reached
▼
create (plan has trial) ┌──────────┐
───────────────────────►│ in_trial │
└────┬─────┘
│ trial_end reached
│ or trial manually ended
▼
create (no trial) ┌────────┐ cancel (end_of_term: true) ┌──────────────┐
───────────────────────►│ active │ ──────────────────────────────────►│ non_renewing │
└───┬────┘ └──────┬───────┘
│ │ term ends
│ pause() │
▼ ▼
┌────────┐ resume() ┌───────────┐
│ paused │◄───────────────────────────────────►│ cancelled │
└────────┘ └───────────┘
│
│ cancel (end_of_term: false)
▼
(cancelled)
The subscription has been created but the start date hasn't arrived yet. No billing has occurred. Use this state to set up subscriptions in advance — for example, when a customer signs up mid-month and you want billing to start on the 1st.
Transitions out of future:
- →
in_trialwhenstart_dateis reached, if the plan has a trial - →
activewhenstart_dateis reached, if the plan has no trial
The trial period is active. The customer has access but has not been charged. An invoice is generated when the trial ends.
What to know:
- Trial end is set by the plan's
trial_periodsetting, or by thetrial_endparameter you passed at creation. - You can end a trial early by calling
subscription.end_trial(). - A customer in
in_trialwith no payment method on file will move toactiveat trial end, but payment collection will fail. Handlepayment_failedwebhooks to recover these.
Transitions out of in_trial:
- →
activewhentrial_endis reached and payment succeeds (orauto_collection: off) - →
cancelledif you cancel during trial
The subscription is billing normally. Chargebee generates an invoice and attempts collection at each current_term_end.
What to know:
current_term_endis the single most important timestamp in an active subscription. Your system should always know this value.- If collection fails at renewal, the subscription stays
activebut the invoice moves topayment_due. Chargebee's dunning workflow handles retries. - A subscription in
activestatus with an unpaid invoice is stillactive— not suspended. You control access logic in your application based on invoice status.
Transitions out of active:
- →
non_renewingif cancelled withend_of_term: true - →
cancelledif cancelled withend_of_term: false - →
pausedif paused
The subscription is scheduled for cancellation at current_term_end. The customer retains access until then. No further invoices will be generated after the current term.
What to know:
- You can reverse a
non_renewingsubscription by callingsubscription.remove_scheduled_cancellation(). It returns toactive. non_renewingis the correct state to show a "cancellation pending" notice in your UI. The customer still has access.
Transitions out of non_renewing:
- →
cancelledwhencurrent_term_endis reached - →
activeif scheduled cancellation is removed
Billing is suspended. The customer does not have access and no invoices are generated while paused. Billing resumes at resume_date.
What to know:
- Pausing extends the billing term by the pause duration.
- Use the
pause()endpoint to pause, andresume()to resume early. - Not all plans support pausing. Configure pause settings at Product Catalog → Plans.
Transitions out of paused:
- →
activewhenresume_dateis reached, or viaresume() - →
cancelledif cancelled while paused
The subscription is terminated. No further billing occurs. A cancelled subscription cannot be reactivated — create a new subscription for the same customer instead.
Transitions out of cancelled:
- None. Terminal state.
Assuming active means paid. A subscription can be active with an overdue invoice. Always check invoice status separately if your access control depends on payment.
// ❌ Wrong — active doesn't mean paid
if (subscription.status === 'active') grantAccess();
// ✅ Correct — check both
const hasAccess =
subscription.status === 'active' || subscription.status === 'in_trial';
const isPaid =
!latestInvoice || latestInvoice.status === 'paid' || subscription.auto_collection === 'off';
if (hasAccess && isPaid) grantAccess();Not handling non_renewing. Customers in non_renewing status still have access. If your access check only looks for active, you'll incorrectly block them.
// ❌ Misses non_renewing customers (they still have access)
const canAccess = subscription.status === 'active';
// ✅ Correct
const ACTIVE_STATES = ['active', 'in_trial', 'non_renewing'];
const canAccess = ACTIVE_STATES.includes(subscription.status);Relying on polling instead of webhooks. Don't poll for subscription status changes. Use webhooks — specifically subscription_activated, subscription_cancelled, subscription_renewed, and payment_failed. See the Webhooks reference.
| Transition | Webhook event |
|---|---|
future → in_trial or active |
subscription_activated |
in_trial → active |
subscription_trial_end_reminder, then subscription_activated |
active → non_renewing |
subscription_scheduled_cancellation |
non_renewing → active |
subscription_scheduled_cancellation_removed |
active or non_renewing → cancelled |
subscription_cancelled |
active → paused |
subscription_paused |
paused → active |
subscription_resumed |
| Renewal invoice generated | invoice_generated |
| Payment collected | payment_succeeded |
| Payment failed | payment_failed |
- Subscription API reference — full endpoint documentation
- Payment gateway errors — handle failed charges in the
activestate - Zero to first charge — Node.js quickstart