Subscriptions overview

Recurring billing relationships between a customer and your prices.

View as MarkdownInstall skills

A subscription bills a customer on a repeating cycle against one or more prices, generating invoices automatically and emitting events you can react to. It is the long-lived object that turns a catalog price into recurring revenue without you scheduling a single charge.

What a subscription isAsk

A subscription (sub_...) ties one customer to one or more prices and owns the schedule on which they are billed. It tracks which billing period you are in, what is owed, and whether the relationship is healthy, in trial, past due, or cancelled. Each period it produces exactly one invoice and hands collection to Payments.

Prop

Type

The lifecycleAsk

A subscription moves through a small set of statuses. Understanding the transitions is the whole job — every feature (trials, dunning, upgrades) is a path between them.

Created

You create the subscription against a customer and a price. If the price has a trial, it enters trialing; otherwise VINR finalizes the first invoice immediately and the status becomes active on payment (or incomplete if the first payment needs action such as 3DS).

Renews

At current_period_end, VINR generates a draft invoice, applies discounts and tax, finalizes, and collects. A successful collection advances the period and keeps the status active.

Recovers or lapses

If collection fails, the subscription goes past_due and enters dunning. A recovered payment returns it to active; an exhausted retry schedule cancels it.

Ends

A cancellation sets canceled. With cancel_at_period_end: true the customer keeps access until the period closes; an immediate cancel stops access at once and optionally prorates a credit.

Create a subscriptionAsk

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

const subscription = await vinr.subscriptions.create({
  customer: 'cust_8Q2v',
  items: [
    { price: 'price_pro_monthly', quantity: 1 },
  ],
  trial_period_days: 14,            // optional; starts in `trialing`
  payment_behavior: 'default_incomplete', // surface SCA before activating
});

console.log(subscription.id);        // "sub_..."
console.log(subscription.status);    // "trialing"

payment_behavior: 'default_incomplete' returns the subscription with a pending payment so you can confirm any required authentication on the client before access is granted. Omit it to let VINR collect synchronously.

Changing a subscriptionAsk

Quantity and price changes are updates to items. By default VINR prorates: it credits the unused portion of the current price and charges the new rate for the remainder of the period, applied to the next invoice.

// Upgrade Pro -> Business, charge the difference immediately.
const updated = await vinr.subscriptions.update('sub_Lm4Z', {
  items: [{ id: 'si_3xY', price: 'price_business_monthly' }],
  proration_behavior: 'always_invoice',
});

Set proration_behavior: 'none' to switch prices at the next renewal with no mid-cycle charge — useful for downgrades you want to take effect only when the customer's paid time runs out.

CancellingAsk

// Let the customer keep access until the period ends.
await vinr.subscriptions.update('sub_Lm4Z', { cancel_at_period_end: true });

// Or end it now and refund unused time as a credit.
await vinr.subscriptions.cancel('sub_Lm4Z', { prorate: true });

Events to react toAsk

Drive fulfilment from webhooks, not from the API response — the response is a snapshot, while events reflect what actually happened during collection and dunning.

EventWhen it firesTypical action
subscription.createdA new subscription is set up.Provision the account.
invoice.paidA period was collected successfully.Extend or confirm access.
invoice.payment_failedCollection failed; dunning begins.Notify the customer.
subscription.updatedStatus, items, or schedule changed.Re-sync entitlements.
subscription.deletedThe subscription ended.Revoke access.
export async function POST(req: Request) {
  const payload = await req.text();
  const signature = req.headers.get('x-vinr-signature')!;
  const event = vinr.webhooks.verify(payload, signature);

  if (event.type === 'subscription.deleted') {
    await revokeAccess(event.data.customer);
  }
  return new Response(null, { status: 200 });
}

Edge casesAsk

Next stepsAsk

Was this page helpful?
Edit on GitHub

Last updated on

On this page