Creating Subscriptions

Forte's Subscriptions API charges your end-users on a recurring monthly or yearly schedule. You define the price (the same inline line items as a one-off Payment); Forte saves the customer's card on the first charge, then automatically re-charges it off-session each cycle, retries failures during a grace period, and keeps you in sync through triggers. Every renewal is recorded as an ordinary Forte Payment, so your existing payment reporting and PAYMENT_COMPLETED triggers work for subscriptions with no extra wiring.

Subscriptions is an early-access feature

Subscriptions must be enabled on your account before createSubscription will work. Request access from the Payments page in the console; until it's granted, createSubscription returns SUBSCRIPTIONS_ACCESS_REQUIRED. Everything else (compliance, live vs. sandbox) works exactly like Payments.

Prerequisites

  • Your project owner has completed compliance registration (live projects must be APPROVED, same as Payments). Sandbox projects bypass this.
  • The Subscriptions feature is enabled on your account (see the callout above).

How a subscription bills

PhaseWhat happens
First chargeConfirmed on-session in the browser with Stripe Elements (exactly like a one-off payment), which also saves the card. The subscription becomes ACTIVE once it succeeds.
RenewalsForte charges the saved card off-session at each interval. No user interaction; you don't call anything.
A renewal failsThe subscription goes PAST_DUE and Forte retries on a grace schedule. The user can fix their card to recover.
Grace exhausted / cancelled / reaches endTimeThe subscription becomes CANCELLED and stops billing.

The state is one of INCOMPLETE (first charge not yet confirmed), ACTIVE, PAST_DUE, or CANCELLED.

Only monthly and yearly cadences are supported

interval is MONTH or YEAR. There is no "every N weeks/days" — a subscription is charged once per interval. Need weekly, daily, or custom cadences? Contact our team.

Creating a subscription

Call createSubscription with line items (the recurring price), a currency, and an interval. Like Payments, there are two surfaces — client-side (the signed-in user subscribing themselves) and server-side (your backend, with an API key). (See API Surfaces.)

Client-side (signed-in user session):

  • POST /api/v1/{projectId}/users/me/subscriptions
  • POST /api/v1/{projectId}/users/me/subscriptions/preview
  • POST /api/v1/{projectId}/users/me/subscriptions/{subscriptionId}/preview

Server-side (API key) — create and manage a specific user's subscriptions:

  • POST /api/v1/projects/{projectId}/users/{userId}/subscriptions
  • POST /api/v1/projects/{projectId}/users/{userId}/subscriptions/preview
  • POST /api/v1/projects/{projectId}/users/{userId}/subscriptions/{subscriptionId}/preview
  • GET/PATCH/DELETE /api/v1/projects/{projectId}/users/{userId}/subscriptions/{subscriptionId}
  • PUT /api/v1/projects/{projectId}/users/{userId}/subscriptions/{subscriptionId}/items
typescript
import { ForteClient } from "@forteplatforms/sdk";
 
const forte = new ForteClient();
 
const result = await forte.users.createSubscription({
  projectId,
  createSubscriptionRequest: {
    currency: "usd",
    interval: "MONTH",
    description: "Pro plan",
    lineItems: [
      { description: "Pro plan", unitAmountCents: 4900, quantity: 1, taxCode: "txcd_10000000" },
    ],
    customerAddress: { line1: "...", city: "...", state: "...", postalCode: "...", country: "US" },
    // paymentMethodId is optional — omit it to let the customer enter a card now (it's saved for renewals).
  },
});
 
const { subscription, stripeClientSecret, stripePublishableKey, stripeConnectedAccountId } = result;
// Confirm `stripeClientSecret` with Stripe Elements (see below) to activate the subscription.

The response mirrors createPayment: a subscription plus stripeClientSecret, stripePublishableKey, and stripeConnectedAccountId. Confirm the client secret with Stripe Elements exactly as you do for a one-off payment (Confirming the payment in the browser) — that single confirmation both charges the first period and saves the card for renewals. The subscription is INCOMPLETE until the confirmation succeeds, then flips to ACTIVE automatically.

Saving the card is automatic

If you omit paymentMethodId, the first charge sets setup_future_usage=off_session, so the card the customer enters in Elements is saved and used for renewals — no separate "save a card" step. If you pass a paymentMethodId (a pm_... the user already saved), that card is used instead.

Previewing the recurring total

createSubscriptionPreview is read-only — it returns subtotalCents, taxCents, amountCents, and the estimated nextRenewalAt, with no charge and nothing persisted. Use it to show "$49.00/month, renews on …" before the customer commits.

Scheduling a future start

Pass startTime (an ISO-8601 timestamp) to begin billing later. Because no one is present at that future moment to confirm a charge, a future startTime requires a saved paymentMethodId (SUBSCRIPTION_PAYMENT_METHOD_REQUIRED otherwise). The create call returns no client secret; Forte makes the first charge off-session at startTime.

Ending automatically

Pass endTime to stop the subscription. The last charge is the last cadence point on or before endTime; after the current paid period runs out, the subscription becomes CANCELLED. Omit endTime to bill indefinitely until you cancel.

Renewals, failures, and the grace period

You don't call anything for renewals — Forte charges the saved card off-session at each interval and records a new Payment.

When a renewal fails (expired card, insufficient funds, etc.) the subscription goes PAST_DUE and Forte retries on a configurable grace schedule (by default a few attempts over several days). To recover, the customer (or your backend) updates the card with a PATCH — Forte then retries immediately on the new card. If every retry in the grace window fails, the subscription is CANCELLED.

Expired or failing cards

A subscription exposes the card on file (cardBrand, cardLast4, cardExpMonth, cardExpYear) and a paymentMethodExpiresBeforeNextRenewal flag so you can prompt the user to update their card before it fails. After it fails, the SUBSCRIPTION_STATUS_CHANGEDPAST_DUE trigger is your signal to ask them to fix it.

Managing a subscription

ActionHow
Change the renewal cardPATCH .../subscriptions/{id} with { paymentMethodId }. The new method is re-validated as belonging to that user; if the subscription is PAST_DUE, the new card is charged immediately to recover it.
Change the billing address (tax)PATCH .../subscriptions/{id} with { customerAddress }. Recomputes tax and takes effect at the next renewal — no charge is made now. Quote the new total first with POST .../subscriptions/{id}/preview (see below).
Change the pricePUT .../subscriptions/{id}/items with the full new lineItems list. The change is staged and takes effect at the next renewal — the current paid period is unchanged.
Cancel at period endPATCH .../subscriptions/{id} with { cancelAtPeriodEnd: true }. Keeps access through the paid period, then stops.
Cancel nowDELETE .../subscriptions/{id}. Stops future renewals immediately; the current period is not refunded.

Acting on a CANCELLED subscription returns a 400. You can also view and manage a user's subscriptions from the Forte console on the user's page.

Refunding a subscription charge cancels the subscription

If you refund a subscription's payment, the subscription is cancelled by default (refunding and then re-billing next cycle is almost never what you want). Pass keepSubscriptionActive=true on the refund to refund a single period and keep billing. A dispute/chargeback on a subscription charge always cancels it.

Changing the billing address used for tax

A subscription stores the customerAddress it was created with, and Forte recalculates tax from that address on every renewal. When a customer moves — or the address was wrong — PATCH .../subscriptions/{id} with a new customerAddress. Forte recomputes the tax and updates the subscription's taxCents and amountCents.

Because Forte never prorates mid-period, a new address takes effect at the next renewal — changing it makes no charge now, so there is no card authorization to decline at edit time. The next off-session renewal charges the saved card the new amount, and follows the usual PAST_DUE → grace-period → retry path if it's declined. (A higher total can, rarely, change the outcome of that renewal, but only at the next renewal — never immediately.) On a PAST_DUE subscription you can send customerAddress together with a new paymentMethodId to recover it on the new card at the corrected total.

An omitted or null customerAddress leaves the existing address unchanged. There's no way to remove the address (and stop charging tax) through this call.

Confirm the new total first. POST .../subscriptions/{id}/preview with the proposed { customerAddress } returns the recalculated subtotalCents, taxCents, amountCents, and the nextRenewalAt the new total applies to — read-only, with no charge and nothing saved. It reuses the subscription's current price and interval, so you send only the address you want to quote. This is the optional "confirm the totals" step for an address change; you can also PATCH directly.

Note this is distinct from POST .../subscriptions/preview (no {subscriptionId}), which quotes a brand-new subscription from full line items; the .../{subscriptionId}/preview form quotes a change to an existing one.

Triggers: what your services receive

Subscriptions reuse the same Payment Triggers you already configure — every renewal is a Payment. There is one new event, SUBSCRIPTION_STATUS_CHANGED, for the two lifecycle moments a payment event doesn't already cover.

EventFires when
PAYMENT_COMPLETEDThe first charge succeeds and every successful renewal (including a recovery charge after a failure). This is your grant/extend-access signal.
SUBSCRIPTION_STATUS_CHANGEDThe subscription transitions to PAST_DUE (ask the user to update their card) or to CANCELLED (revoke access). The state field tells you which.
PAYMENT_REFUNDEDA subscription charge is refunded/disputed through Stripe (same rules as one-off payments — your own API-initiated refunds don't fire it).

There is no separate "subscription created" event. Activation is signalled by the first PAYMENT_COMPLETED, so you grant access the same way you would for a one-off payment.

The exact events for each moment:

  • On create / activation → one PAYMENT_COMPLETED (for the first charge). Nothing else.
  • On each renewal → one PAYMENT_COMPLETED.
  • When a renewal fails (enters the grace period) → one SUBSCRIPTION_STATUS_CHANGED with state: "PAST_DUE", fired once on the ACTIVEPAST_DUE transition (not once per retry). When a later retry (or a card update) succeeds, you get a single PAYMENT_COMPLETED — there is no separate "recovered"/"active again" event; the successful payment is the recovery signal.
  • On cancellation → one SUBSCRIPTION_STATUS_CHANGED with state: "CANCELLED" (whether the cancel came from DELETE, cancel-at-period-end, exhausting the grace period, or endTime). If the cancellation was caused by a refund/dispute, you also receive the corresponding PAYMENT_REFUNDED.

Trigger body

The POST body is the same shape as a payment trigger, with a few subscription-aware fields:

json
{
  "event": "SUBSCRIPTION_STATUS_CHANGED",
  "userId": "usr_...",
  "subscriptionId": "sub_...",
  "paymentTime": "2026-05-10T18:42:11.123Z",
  "state": "PAST_DUE"
}
  • eventPAYMENT_COMPLETED, PAYMENT_REFUNDED, or SUBSCRIPTION_STATUS_CHANGED.
  • state — for payment events, COMPLETED/REFUNDED; for subscription events, PAST_DUE/CANCELLED.
  • paymentId is present on payment events; subscriptionId is present on subscription events. Renewal PAYMENT_COMPLETED events carry both, so you can attribute a payment to its subscription.

Make handlers idempotent on (event, paymentId|subscriptionId, state). Delivery, the X-Forte-Trusted header, and the retry policy are identical to payment triggers.

Sandbox vs. live

Subscriptions follow the same routing as Payments — sandbox projects charge the sandbox connected account in Stripe Test mode; live projects charge real cards once compliance is APPROVED.

Search

Search documentation and console pages