Subscription lifecycle

Every subscription status and transition.

View as MarkdownInstall skills

A subscription is a state machine. Knowing which status it is in — and what moves it to the next one — tells you whether to grant access, retry a payment, or wind down service. This page documents every status, the transitions between them, and the events that signal each move.

The status fieldAsk

Every subscription carries a status that reflects the outcome of its latest billing attempt. Read it from the object or from the webhook payload; never infer it from a payment alone.

Prop

Type

How transitions happenAsk

Status changes are driven by billing outcomes, not by direct writes. The diagram below reads top to bottom — creation on the left, terminal states at the bottom.

                 create

        ┌──────────┴──────────┐
   has trial?              no trial
        │                     │
   trialing ──trial ends──► incomplete ──auth ok──► active
                               │                      │
                          auth fails              invoice fails
                               │                      │
                      incomplete_expired          past_due ──retries ok──► active

                                                 retries fail

                                            unpaid / canceled

Creation

Creating a subscription with a recurring price either starts a trial (trialing) or immediately attempts the first invoice. If that first payment needs customer action — 3DS, for example — the subscription parks in incomplete until the payment succeeds or the 23-hour window lapses into incomplete_expired.

Renewal

At each billing cycle anchor the subscription finalizes an invoice and collects it. Success keeps it active; failure moves it to past_due and starts dunning.

Recovery or cancellation

While past_due, VINR retries on your dunning schedule. A recovered payment returns the subscription to active. If retries are exhausted, it lands in canceled or unpaid depending on your collection.exhausted_behavior setting.

Cancellation: now vs. period endAsk

Cancellation is the one transition you trigger directly. You choose whether it takes effect immediately or at the end of the paid period.

import { Vinr } from '@vinr/sdk';
const vinr = new Vinr({ secretKey: process.env.VINR_SECRET_KEY });

// Cancel at period end — keeps access until the customer's paid time runs out.
const sub = await vinr.subscriptions.update('sub_8Qk2x', {
  cancelAtPeriodEnd: true,
});
console.log(sub.status);            // still "active"
console.log(sub.cancelAtPeriodEnd); // true

// Cancel immediately — stops billing now, optionally prorating a credit.
await vinr.subscriptions.cancel('sub_8Qk2x', {
  prorate: true,                    // credit unused time as a refund
});                                 // status -> "canceled"

cancelAtPeriodEnd: true does not change status — the subscription stays active until the period ends, then transitions to canceled on its own. To undo, set cancelAtPeriodEnd: false before the period closes.

Pausing instead of cancelingAsk

Pausing keeps the subscription and its history but stops generating invoices — useful for seasonal accounts or retention offers. Resume restores billing from the next cycle.

await vinr.subscriptions.update('sub_8Qk2x', {
  pauseCollection: { behavior: 'void' }, // skip invoices while paused
});                                        // status -> "paused"

await vinr.subscriptions.update('sub_8Qk2x', {
  pauseCollection: null,                   // resume
});                                        // status -> "active"

Events to subscribe toAsk

Drive your access logic from events, not from polling. Each transition emits a typed event; verify every payload before acting.

EventFires whenTypical action
subscription.createdA subscription is first createdProvision the account
subscription.trial_will_end3 days before a trial endsPrompt for a payment method
subscription.updatedStatus, plan, or quantity changesReconcile entitlements
invoice.paidA renewal collects successfullyExtend access
invoice.payment_failedA renewal fails (now past_due)Notify; let dunning run
subscription.deletedA subscription reaches canceledRevoke access
// Express-style webhook handler.
app.post('/webhooks/vinr', async (req, res) => {
  const event = vinr.webhooks.verify(
    req.rawBody,
    req.headers['x-vinr-signature'],
  );

  switch (event.type) {
    case 'subscription.deleted':
      await revokeAccess(event.data.object.customer);
      break;
    case 'invoice.payment_failed':
      await flagPastDue(event.data.object.subscription);
      break;
  }
  res.sendStatus(200);
});

Edge casesAsk

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page