> ## Documentation Index
> Fetch the complete documentation index at: https://docs.clinkbill.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Subscriptions

> Recurring prices, the first subscription, renewals, cancellation, and plan changes.

This page covers recurring billing. Orders, Sessions, and webhooks work exactly as they do for one-time payments, so read [Hosted Checkout](/build-integration) first if you have not.

## Pick a path

Three situations, three different endpoints:

| Situation                                                                                              | Path                               | Notes                                                                                            |
| ------------------------------------------------------------------------------------------------------ | ---------------------------------- | ------------------------------------------------------------------------------------------------ |
| A new customer, or one without a reusable payment instrument                                           | Recurring price + Checkout Session | **Recommended.** The customer picks and authorizes a payment method in checkout                  |
| You already have a Clink Customer and a usable Payment Instrument                                      | `POST /subscription`               | Your server creates the subscription and starts the first payment directly                       |
| An active subscription where the customer wants to change cards, cancel, switch plans, or see invoices | `POST /billing/session`            | Send them to the Customer Portal first; use the query and update APIs for server-side automation |

<Note>
  `POST /checkout/session` and `POST /subscription` are not two spellings of the same call. The first gives the customer an interactive checkout; the second is for a server that already has both a customer and a payment instrument.
</Note>

## 1. Prepare a recurring product and price

Subscriptions **require** a pre-registered Product and a recurring price. The inline `priceDataList` approach cannot create one.

```json theme={null}
{
  "productId": "prd_xxxxx",
  "currency": "USD",
  "unitAmount": 29.99,
  "priceType": "recurring",
  "recurringDetails": {
    "interval": "month",
    "intervalCount": 1,
    "trialPeriodDays": 7,
    "pricingModel": "flat_rate"
  },
  "isDefaultPrice": true
}
```

`interval` accepts `day`, `week`, `month`, `year`, `quarter`, `half_year`, and `custom`.

<Note>
  `quarter` and `half_year` are resolved by the platform — do not convert them into 3 or 6 months yourself. `intervalCount` only means a number of days when `interval` is `custom`.
</Note>

Examples on this page use a monthly `flat_rate` price. For other pricing models, see the field reference in [Create price](/api-reference/endpoint/create-price).

## 2. Recommended: create the first subscription through Checkout

The shape is identical to a one-time payment — your backend creates the Session, the frontend opens checkout, webhooks update local state. Only the item changes, from an inline product to a recurring price.

```json theme={null}
{
  "customerEmail": "buyer@example.com",
  "merchantReferenceId": "membership_10001",
  "productId": "prd_xxxxx",
  "priceId": "price_xxxxx",
  "originalAmount": 29.99,
  "originalCurrency": "USD",
  "uiMode": "hostedPage",
  "successUrl": "https://merchant.example.com/subscription/success",
  "cancelUrl": "https://merchant.example.com/subscription/cancel"
}
```

Amount and currency are still required, and the backend checks them against the Product and Price.

<Warning>
  At the moment you create the Session, **no subscription exists and no money has been collected**.

  Once the customer submits payment, checkout follows the recurring price and produces a Subscription, an Invoice, and an Order. Returning to `successUrl` only means the interaction ended — read the final state from the API and from signature-verified webhooks.
</Warning>

## 3. Server-side: create the subscription directly

Use this when the customer already has a Clink Customer record and a usable payment instrument.

```json theme={null}
{
  "customerId": "cus_xxxxx",
  "merchantReferenceId": "membership_10002",
  "productId": "prd_xxxxx",
  "priceId": "price_xxxxx",
  "paymentInstrumentId": "pi_xxxxx",
  "paymentMethodType": "CARD",
  "paymentCurrency": "USD",
  "returnUrl": "https://merchant.example.com/subscription/return"
}
```

Supply at least one of `customerId`, `customerEmail`, or `referenceCustomerId`. The recurring price must belong to your merchant and support the `paymentCurrency` you send.

<Note>
  Whether a given payment method works for subscriptions also depends on your channel configuration and currency. Verify each method your account has actually enabled rather than assuming a shared list.
</Note>

The numeric `status` in the response describes **this payment attempt**:

| `status` | Meaning                          | What to do                                                                                                           |
| -------- | -------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `1`      | This payment succeeded           | Wait for the matching webhook and handle it idempotently. **This response alone is not grounds for granting access** |
| `2`      | In progress                      | Show a pending state and keep polling. Do not treat it as failure and charge again                                   |
| `3`      | Failed                           | Record the result and let the customer switch methods or retry                                                       |
| `5`      | The customer needs one more step | Follow the returned `action` for a redirect, QR code, or other verification                                          |

The response also returns `subscriptionId`, `sessionId`, `paymentInstrumentId`, `orderId`, and `invoiceId`. **Store all five on your own subscription record** — one field alone will not be enough for later queries and reconciliation.

## 4. Mapping subscription status to access

This is where subscription integrations most often go wrong. Payment status and entitlement status are two different things.

| Subscription status  | Meaning                                              | What to do with access                                                                                |
| -------------------- | ---------------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
| `incomplete`         | Subscription created, first payment not finished     | **Do not grant paid access**                                                                          |
| `free_trial`         | Inside the free trial                                | Grant access under your trial rules and record `trialEnd`                                             |
| `active`             | Valid with no outstanding invoice                    | Keep access open                                                                                      |
| `past_due`           | Renewal failed, retries may still happen             | Apply your grace period policy. **This is not paid, and it is not a reason to start a second charge** |
| `incomplete_expired` | The first subscription never completed and timed out | Close out the unfinished flow                                                                         |
| `cancelled`          | Subscription ended                                   | Revoke access at the actual end time                                                                  |

<Warning>
  **Do not use `subscription.created` as the signal to grant paid access.** It only means the subscription record exists.

  Free trials are driven by `subscription.trialing`. First payments and renewals need `subscription.activated`, `invoice.paid`, and query results, applied idempotently.

  Entitlement changes belong on your own subscription and invoice records, not on a single order row — a subscription is an ongoing relationship and one order number cannot hold it.
</Warning>

## 5. Events to subscribe to

The `order.*` events from one-time payments still apply. Subscriptions add these.

**Lifecycle**

`subscription.created`, `subscription.trialing`, `subscription.activated`, `subscription.past_due`, `subscription.incomplete_expired`, `subscription.cancelled`

**Plan changes**

`subscription.updated.plan_changed`, `subscription.updated.plan_change_canceled`, `subscription.updated.renewed`, `subscription.updated.cancel_at_period_end_set`, `subscription.updated.cancel_at_period_end_revoked`

**Invoices**

`invoice.open`, `invoice.paid`, `invoice.void`

<Warning>
  Subscribe with full event names. Wildcards like `subscription.*` and `invoice.*` **are not values the API accepts**.
</Warning>

Events are retried and arrive out of order. Your handler must at least:

* Deduplicate atomically on `event.id`
* Resolve out-of-order arrivals by subscription status precedence, so an older event cannot overwrite a newer state
* Run fulfillment for a given `invoiceId` exactly once

Signature verification, deduplication, and order matching are covered in [Hosted Checkout](/build-integration).

## 6. Cancellation

```json theme={null}
{
  "reason": "Customer requested cancellation",
  "cancelReasonCode": "no_longer_needed",
  "cancelImmediately": false
}
```

`reason` is required, 1 to 255 characters. `cancelReasonCode` is optional and accepts `too_expensive`, `need_more_features`, `found_alternative`, `no_longer_needed`, `poor_customer_service`, `poor_usability`, `poor_quality`, and `other_reasons`.

Omitting `cancelImmediately` or sending `false` cancels **at the end of the current period**. Sending `true` cancels immediately.

<Warning>
  **Cancelling is not refunding.** An immediate cancellation does not return money already collected for the current period. Refunds go through the [refund API](/api-reference/endpoint/create-refund) separately.
</Warning>

## 7. Plan changes

Preview first, then confirm. Both steps require `quantity`.

**Step one — preview**

```json theme={null}
{
  "priceId": "price_yyyyy",
  "quantity": 1,
  "promotionCode": "WELCOME20"
}
```

**Step two — confirm with the `priceSnapshotId` from the preview**

```json theme={null}
{
  "priceSnapshotId": "snap_xxxxx",
  "quantity": 1,
  "promotionCode": "WELCOME20"
}
```

If you use a promotion code, send **the same code** in both calls or the amounts will not line up.

`immediate: true` may produce a prorated charge and return an `action`. `false` schedules the change for the next billing boundary. A pending change can be withdrawn with `POST /subscription/{id}/update/cancel`.

<Note>
  The current API Reference request examples for preview and confirm omit `quantity`, but the server requires it. Follow this page and send it in both calls.
</Note>

## 8. Scheduled phases

`scheduledPhases` lets a subscription switch plans automatically at future renewal boundaries, up to 10 phases, with `sequence` and `effectiveCycle` both starting at `1` and strictly increasing.

This is an advanced capability you will not need for a first integration. Field details are in [Checkout Session](/guides/payments/checkout_session).

## Next

<CardGroup cols={2}>
  <Card title="Discounts and promotion codes" icon="ticket-percent" href="/promotions">
    Adding discounts, and how many periods they last.
  </Card>

  <Card title="Go live" icon="rocket" href="/go-live">
    Subscription checks before production.
  </Card>
</CardGroup>
