Stripe | Financial Infrastructure to Grow Your Revenue

Stripe | Financial Infrastructure to Grow Your Revenue

5282 articles

Build a subscriptions integration


Build a subscriptions integration

Create and manage subscriptions to accept recurring payments.

Checkout

Elements

Mobile

Integration effort

Some code

Integration type

Combine UI components into a custom payment flow

UI customisation

CSS-level customisation with the Appearance API

Try it out

Build a custom payment form using Stripe Elements and the Checkout Sessions API to sell fixed-price subscriptions. See how this integration compares to Stripe’s other integration types.

The Checkout Sessions API provides built-in support for tax calculation, discounts, shipping and currency conversion, reducing the amount of custom code you need to write. This is the recommended approach for most integrations. Learn more about when to use Checkout Sessions instead of PaymentIntents.

If you don’t want to build a custom payment form, you can integrate with the hosted version of Checkout. For an immersive version of that end-to-end integration guide, see the Billing quickstart.

If you aren’t ready to code an integration, you can set up basic subscriptions manually in the Dashboard. You can also use Payment Links to set up subscriptions without writing any code. Learn more about designing an integration to understand the decisions you need to make and the resources you need.

What you’ll build

This guide shows you how to:

  • Model your business by building a product catalogue.
  • Build a registration process that creates a customer.
  • Create subscriptions and collect payment information.
  • Test and monitor payment and subscription status.
  • Let customers change their plan or cancel the subscription.

API object definitions

Set up Stripe

Install the Stripe client of your choice:

Command Line

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

# Available as a gem
sudo gem install stripe

Gemfile

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

# If you use bundler, you can add this line to your Gemfile
gem 'stripe'

And then install the Stripe CLI. The CLI provides webhook testing and you can run it to make API calls to Stripe. This guide shows how to use the CLI to set up a pricing model in a later section.

Install the Stripe CLI with npm:

Command Line

npm install -g @stripe/cli@latest

After installation, log in to your Stripe account:

Command Line

stripe login

After you install the CLI, you can also install agent tooling, or set up autocompletion.

Note

For more installation options for Windows, macOS, Linux and Docker, see the Stripe CLI readme on GitHub.

Create the pricing model Stripe CLI or Dashboard

Recurring pricing models represent the products or services you sell, how much they cost, what currency you accept for payments, and the service period for subscriptions. To build the pricing model, create products (what you sell) and prices (how much and how often to charge for your products).

This example uses flat-rate pricing with two different service-level options: Basic and Premium. For each service-level option, you need to create a product and a recurring price. To add a one-time charge for something like a setup fee, create a third product with a one-time price.

Each product bills at monthly intervals. The price for the Basic product is 5 USD. The price for the Premium product is 15 USD. See the flat rate pricing guide for an example with three tiers.

Go to the Add a product page and create two products. Add one price for each product, each with a monthly recurring billing period:

  • Premium product: Premium service with extra features
  • Price: Flat rate | 15 USD
  • Basic product: Basic service with minimum features
  • Price: Flat rate | 5 USD

After you create the prices, record the price IDs so you can use them in other steps. Price IDs look like this: price_G0FvDp6vZvdwRZ.

When you’re ready, use the Copy to live mode button at the top right of the page to clone your product from a sandbox to live mode.

Create the customer Client and Server

Stripe needs a customer for each subscription. In your application front end, collect any necessary information from your users and pass it to the back end.

If you need to collect address details, the Address Element enables you to collect a shipping or billing address for your customers. For more information on the Address Element, see the Address Element page.

register.html

<form id="signup-form">
 <label>
 Email
 <input id="email" type="email" placeholder="Email address" value="test@example.com" required />
 </label>

 <button type="submit">
 Register
 </button>
</form>

register.js

const emailInput = document.querySelector('#email');

fetch('/create-customer', {
 method: 'post',
 headers: {
 'Content-Type': 'application/json',
 },
 body: JSON.stringify({
 email: emailInput.value,
 }),
}).then(r => r.json());

On the server, create an object to represent the customer. This can be either a customer-configured Account object or a Customer object. Save the object’s ID to use in the Checkout Session.

Use the Accounts v2 API to represent customers

The Accounts v2 API is generally available for Connect users, and in public preview for other Stripe users. If you’re part of the Accounts v2 preview, you need to specify a preview version in your code.

To join the Accounts v2 preview, go to Account previews and features in your Dashboard and enable Reusable payment methods for Global Payouts.

For most use cases, we recommend modelling your customers as customer-configured Account objects instead of using Customer objects.

Command Line

Select a language

cURL

Stripe CLI

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

Create a Checkout Session Server

On the back end of your application, define an endpoint that creates the session for your front end to call. You’ll need the price ID of the subscription the customer is signing up for – your front end passes this value.

If you created a one-off price in step 2, pass that price ID also. After creating a Checkout Session, make sure you pass the client secret back to the client in the response.

Note

You can use lookup_keys to fetch prices rather than Price IDs. See the sample application for an example.

Select a language

Ruby

Python

.NET

PHP

Java

Node.js

Go

No results

From your Dashboard, enable the payment methods you want to accept from your customers. Checkout supports several payment methods.

Initialise Checkout Client

Call initCheckoutElementsSdk, passing in clientSecret.

initCheckoutElementsSdk returns a Checkout object that contains data from the Checkout Session and methods to update it.

Read the total and lineItems from actions.getSession(), and display them in your UI. This lets you turn on new features with minimal code changes. For example, adding manual currency prices requires no UI changes if you display the total.

checkout.html

<div id="checkout-container"></div>

checkout.js

const clientSecret = fetch('/create-checkout-session', {method: 'POST'})
 .then((response) => response.json())
 .then((json) => json.client_secret);

const checkout = stripe.initCheckoutElementsSdk({clientSecret});
const loadActionsResult = await checkout.loadActions();

if (loadActionsResult.type === 'success') {
 const session = loadActionsResult.actions.getSession();
 const checkoutContainer = document.getElementById('checkout-container');

 checkoutContainer.append(JSON.stringify(session.lineItems, null, 2));
 checkoutContainer.append(document.createElement('br'));
 checkoutContainer.append(`Total: ${session.total.total.amount}`);
}

Collect payment information Client

Collect payment details on the client with the Payment Element. The Payment Element is a pre-built UI component that simplifies collecting payment details for a variety of payment methods.

The Payment Element contains an iframe that securely sends payment information to Stripe over an HTTPS connection. Avoid placing the Payment Element within another iframe because some payment methods require redirecting to another page for payment confirmation.

If you choose to use an iframe and want to accept Apple Pay or Google Pay, the iframe must have the allow attribute set to equal "payment *".

The checkout page address must start with https:// rather than http:// for your integration to work. You can test your integration without using HTTPS, but remember to enable it when you’re ready to accept live payments.

First, create a container DOM element to mount the Payment Element. Then create an instance of the Payment Element using checkout.createPaymentElement and mount it by calling element.mount, providing either a CSS selector or the container DOM element.

checkout.html

<div id="payment-element"></div>

checkout.js

const paymentElement = checkout.createPaymentElement();
paymentElement.mount('#payment-element');

See the Stripe.js docs to view the supported options.

You can customise the appearance of all Elements by passing elementsOptions.appearance when initialising Checkout on the front end.

Submit the payment Client-side

Render a Pay button that calls confirm from the Checkout instance to submit the payment.

checkout.html

<button id="pay-button">Pay</button>
<div id="confirm-errors"></div>

checkout.js

const checkout = stripe.initCheckoutElementsSdk({clientSecret});

checkout.on('change', (session) => {
 document.getElementById('pay-button').disabled = !session.canConfirm;
});

const loadActionsResult = await checkout.loadActions();

if (loadActionsResult.type === 'success') {
 const {actions} = loadActionsResult;
 const button = document.getElementById('pay-button');
 const errors = document.getElementById('confirm-errors');
 button.addEventListener('click', () => {
 // Clear any validation errors
 errors.textContent = '';

 actions.confirm().then((result) => {
 if (result.type === 'error') {
 errors.textContent = result.error.message;
 }
 });
 });
}

Listen for webhooks Server

To complete the integration, you need to process webhooks sent by Stripe. These events are triggered whenever the status in Stripe changes, such as subscriptions creating new invoices. In your application, set up an HTTP handler to accept a POST request containing the webhook event and verify the signature of the event:

server.rb

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

During development, use the Stripe CLI to observe webhooks and forward them to your application. Run the following in a new terminal while your development app is running:

Command Line

stripe listen --forward-to localhost:4242/webhook

For production, set up a webhook endpoint URL in the Dashboard or use the Webhook Endpoints API.

You need to listen to a few events to complete the remaining steps in this guide. See Subscription events for more details about subscription-specific webhooks.

Provision access to your service Client and Server

Now that the subscription is active, give your user access to your service. To do this, listen to the customer.subscription.created, customer.subscription.updated, and customer.subscription.deleted events. These events pass a subscription object that contains a status field indicating whether the subscription is active, past due or cancelled. See the subscription lifecycle for a complete list of statuses.

In your webhook handler:

  1. Verify the subscription status. If it’s active then your user has paid for your product.
  2. Check the product the customer subscribed to and grant access to your service. Checking the product instead of the price gives you more flexibility if you need to change the pricing or billing period.
  3. Store the product. id , subscription. id and subscription. status in your database along with either the customer _ account. id or the customer. id you already saved. Check this record when determining which features to enable for the user in your application.

The status of a subscription might change at any point during its lifetime, even if your application doesn’t directly make any calls to Stripe. For example, a renewal might fail because of an expired credit card, which puts the subscription into a past due status. Or, if you implement the customer portal, a user might cancel their subscription without directly visiting your application. Implementing your handler correctly keeps your application status in sync with Stripe.

Cancel the subscription Client and Server

It’s common to allow customers to cancel their subscriptions. This example adds a cancellation option to the account settings page.

Account settings with the ability to cancel the subscription

script.js

function cancelSubscription(subscriptionId) {
 return fetch('/cancel-subscription', {
 method: 'post',
 headers: {
 'Content-Type': 'application/json',
 },
 body: JSON.stringify({
 subscriptionId: subscriptionId,
 }),
 })
 .then(response => {
 return response.json();
 })
 .then(cancelSubscriptionResponse => {
 // Display to the user that the subscription has been canceled.
 });
}

On the back end, define the endpoint for your front end to call.

server.rb

Select a language

Ruby

Python

PHP

Java

Node.js

Go

.NET

No results

# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new('sk_test_Ou1w6LVt3zmVipDVJsvMeQsc')

post '/cancel-subscription' do
 content_type 'application/json'
 data = JSON.parse request.body.read

 deleted_subscription = client.v1.subscriptions.cancel(data['subscriptionId'])

 deleted_subscription.to_json
end

Your application receives a customer.subscription.deleted event.

After the subscription is cancelled, update your database to remove the Stripe subscription ID you previously stored and limit access to your service.

When a subscription cancels, you can’t reactivate it. Instead, collect updated billing information from your customer, update their default payment method and create a new subscription with their existing customer record.

Test your integration

Test payment methods

Use the following table to test different payment methods and scenarios.

Payment methodScenarioHow to test
the related setting Direct DebitYour customer successfully pays with the related setting Direct Debit.Fill out the form using the account number 900123456 and BSB 000000. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
the related setting Direct DebitYour customer’s payment fails with an account_closed error code.Fill out the form using the account number 111111113 and BSB 000000.
Credit cardThe card payment succeeds and doesn’t require authentication.Fill out the credit card form using the credit card number 4242 4242 4242 4242 with any expiration, CVC, and postal code.
Credit cardThe card payment requires authentication.Fill out the credit card form using the credit card number 4000 0025 0000 3155 with any expiration, CVC, and postal code.
Credit cardThe card is declined with a decline code like insufficient_funds.Fill out the credit card form using the credit card number 4000 0000 0000 9995 with any expiration, CVC, and postal code.
the related setting Direct DebitYour customer successfully pays with the related setting Direct Debit.Fill out the form using the account number the related setting. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
the related setting Direct DebitYour customer’s PaymentIntent status transitions from processing to requires_payment_method.Fill out the form using the account number the related setting.

Monitor events

Set up webhooks to listen to subscription change events, such as upgrades and cancellations. Learn more about subscription webhooks. You can view events in the Dashboard or with the Stripe CLI.

For more details, see testing your Billing integration.

Optional Let customers change their plans Client and Server

Optional Preview a price change Client and Server

Optional Display the customer payment method Client and Server

Optional Set the billing cycle date Server

Disclose Stripe to your customers

Stripe collects information on customer interactions with Elements to provide services to you, prevent fraud, and improve its services. This includes using cookies and IP addresses to identify which Elements a customer saw during a single checkout session. You’re responsible for disclosing and obtaining all rights and consents necessary for Stripe to use data in these ways. For more information, visit our privacy center.

Last verified 2026-09-25

Is this helpful?