Easy Labs
BillingGuides

Create a subscription

Start a recurring charge agreement against a saved payment instrument.

Goal

Create an active (or trialing) subscription that charges a customer's saved payment instrument on a fixed cadence. Use this whenever you sell a recurring plan — SaaS seats, monthly memberships, repeat-fulfillment products. For one-off invoices, see Send an invoice. For self-serve plan changes after the subscription exists, see Launch the customer portal.

Prerequisites

  • A sandbox or production API key
  • @easylabs/node installed
  • A Customer and a saved payment instrument belonging to that customer
  • A recurring Price (recurring: true)

Implementation

1. Resolve or create the price

Subscriptions charge against a price ID, not a product ID. If you don't already have one, create the price (or look it up) before continuing:

import { createClient } from "@easylabs/node";

const easy = await createClient({ apiKey: process.env.EASY_API_KEY! });

const { data: price } = await easy.createPrice({
  product_id: "prod_01HXXXXXXXXXXX",
  active: true,
  recurring: true,
  currency: "USD",
  unit_amount: 4900,            // $49.00
  interval: "month",
  interval_count: 1,
  tax_behavior: "exclusive",
});

2. Create the subscription

Pass identity_id, instrument_id, and one or more items. The first invoice is generated synchronously; the call resolves once the subscription is active (or trialing if you supply a trial).

const { data: subscription } = await easy.createSubscription({
  identity_id: "cus_01HXXXXXXXXXXX",
  instrument_id: "pi_01HXXXXXXXXXXX",
  items: [{ price_id: price.id, quantity: 3 }],
  metadata: { workspace_id: "ws_42" },
});

To start with a free trial, omit instrument_id and supply trial_period_days (or an explicit trial_end ISO datetime):

await easy.createSubscription({
  identity_id: "cus_01HXXXXXXXXXXX",
  items: [{ price_id: price.id, quantity: 1 }],
  trial_period_days: 14,
});

3. React to lifecycle events

Wire your webhook endpoint to act on subscription.created, subscription.updated, subscription.trial_will_end, invoice.paid, and invoice.payment_failed. See the webhooks reference for signature verification with EasyWebhooks.constructEvent.

4. Mutate later

Add a seat: await easy.addSubscriptionItem(subscription.id, { price_id, quantity: 1 }). Change quantity: updateSubscriptionItem(subscription.id, itemId, { quantity: 5 }). Cancel at period end: updateSubscription(subscription.id, { cancel_at_period_end: true }). Cancel immediately: cancelSubscription(subscription.id). Preview the proration impact of a change first with getSubscriptionProrationPreview.

Tradeoffs

  • Trial without an instrument is fine, but the subscription will move to incomplete at trial end if no instrument_id has been attached. Schedule a reminder before trial_end and patch the subscription with updateSubscription({ instrument_id }).
  • Proration behavior defaults to create_prorations on item changes. Pass proration_behavior: "none" for grandfathered customers, or "always_invoice" to bill the proration immediately rather than rolling into the next cycle.
  • Skip the subscription engine entirely when billing is per-event or quote-style — use createInvoice instead and avoid the cycle bookkeeping.

On this page