Skip to content
Create account or Sign in
The Stripe Docs logo
/
Ask AI
Create accountSign in
Get started
Payments
Revenue
Platforms and marketplaces
Money management
Developer resources
APIs & SDKsHelp
OverviewAccept a paymentUpgrade your integration
Online payments
OverviewFind your use case
Use Payment Links
Build a payments page
Build a custom integration with Elements
    Overview
    Quickstart guides
    Stripe Elements
    Compare Checkout Sessions and PaymentIntents
    Design an advanced integration
    Customize look and feel
    Manage payment methods
    Collect additional information
    Build a subscriptions integration
    Dynamic updates
    Add discounts
    Collect taxes on your payments
    Collect surcharges
    Redeem credits
    Let customers pay in their local currency
    Save and retrieve customer payment methods
    Manually approve payments on your server
    Authorize and capture a payment separately
    Elements with Checkout Sessions API beta changelog
Build an in-app integration
Use Managed Payments
Recurring payments
In-person payments
Terminal overview
Availability
Readers
No code
Custom integration
Payment methods
Add payment methods
Manage payment methods
Faster checkout with Link
Payment operations
Analytics
Balances and settlement time
Compliance and security
Currencies
Declines
Disputes
Radar
Payouts
ReceiptsRefunds and cancellations
Advanced integrations
Custom payment flows
Flexible acquiring
Off-Session Payments
Multiprocessor orchestration
Beyond payments
Incorporate your company
Crypto
Agentic commerce
Financial Connections
Climate
Verify identities
United States
English (United States)
  1. Home/
  2. Payments/
  3. Build a custom integration with Elements

Build a subscriptions integration with Elements

Create and manage subscriptions to accept recurring payments with Elements.

Fixed-price subscription page with the Payment Element

Integration effort

Some code

Integration type

Combine UI components into a custom payment flow

UI customization

CSS-level customization 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 catalog.
  • 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
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
# Available as a gem sudo gem install stripe
Gemfile
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

Note

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

After installation, connect the CLI to your Stripe account by logging in:

Command Line
stripe login

The CLI displays a pairing code and opens the Stripe Dashboard, where you approve access to accounts, sandboxes, and live mode. The CLI stores your credentials in your operating system’s secure credential store (when available) and refreshes the session automatically. To authenticate with an API key instead, run stripe login --interactive, use stripe config, pass --api-key, or set the STRIPE_API_KEY environment variable.

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="[email protected]" 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 modeling your customers as customer-configured Account objects instead of using Customer objects.

Command Line
cURL
Stripe CLI
Ruby
Python
PHP
Java
Node.js
Go
.NET
No results
curl -X POST https://api.stripe.com/v2/core/accounts \ -H "Authorization: Bearer sk_test_BQokikJOvBiI2HlWgH4olfQ2" \ -H "Stripe-Version: 2026-04-22.preview" \ --json '{ "contact_email": "[email protected]", "display_name": "Jenny Rosen", "identity": { "individual": { "given_name": "Jenny Rosen", "address": { "city": "San Francisco", "country": "US", "line1": "123 Main Street", "postal_code": "94605", "state": "CA" } } }, "configuration": { "customer": { "capabilities": { "automatic_indirect_tax": { "requested": true } }, "shipping": { "address": { "city": "San Francisco", "country": "US", "line1": "123 Main Street", "postal_code": "94605", "state": "CA" } } } }, "include": [ "configuration.customer", "identity" ] }'

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-time 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.

Ruby
Python
.NET
PHP
Java
Node.js
Go
No results
require 'stripe' require 'sinatra' # This test secret API key is a placeholder. Don't include personal details in requests with this key. # To see your test secret API key embedded in code samples, sign in to your Stripe account. # You can also find your test secret API key at https://dashboard.stripe.com/test/apikeys. # Don't put any keys in code. See https://docs.stripe.com/keys-best-practices. Stripe.api_key = 'sk_test_BQokikJOvBiI2HlWgH4olfQ2' Stripe.api_version = '2026-08-26.dahlia' set :static, true set :port, 4242 YOUR_DOMAIN = 'http://localhost:3000' post '/create-checkout-session' do content_type 'application/json' session = Stripe::Checkout::Session.create({ ui_mode: 'elements', # Provide the Account ID of the account you previously created customer_account: '{{CUSTOMER_ACCOUNT_ID}}', line_items: [{ # Provide the exact Price ID (for example, price_1234) of the product you want to sell price: '{{PRICE_ID}}', quantity: 1, }], mode: 'subscription', return_url: YOUR_DOMAIN + '/return?session_id={CHECKOUT_SESSION_ID}', }) { clientSecret: session.client_secret }.to_json end

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

Initialize 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 prebuilt 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 customize the appearance of all Elements by passing elementsOptions.appearance when initializing 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
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_BQokikJOvBiI2HlWgH4olfQ2') post '/webhook' do # You can use webhooks to receive information about asynchronous payment events. # For more about our webhook events check out https://stripe.com/docs/webhooks. webhook_secret = ENV['STRIPE_WEBHOOK_SECRET'] payload = request.body.read if !webhook_secret.empty?

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 canceled. 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.

Sample subscription cancelation interface.

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
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_BQokikJOvBiI2HlWgH4olfQ2') 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 cancels, 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
BECS Direct DebitYour customer successfully pays with BECS 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.
BECS 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.
SEPA Direct DebitYour customer successfully pays with SEPA Direct Debit.Fill out the form using the account number AT321904300235473204. The confirmed PaymentIntent initially transitions to processing, then transitions to the succeeded status three minutes later.
SEPA Direct DebitYour customer’s PaymentIntent status transitions from processing to requires_payment_method.Fill out the form using the account number AT861904300235473202.

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.

OptionalLet customers change their plans
Client and Server

OptionalPreview a price change
Client and Server

OptionalDisplay the customer payment method
Client and Server

OptionalSet 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.

Was this page helpful?
YesNo
  • Need help? Contact Support.
  • Chat with Stripe developers on Discord.
  • Check out our changelog.
  • Questions? Contact Sales.
  • LLM? Read llms.txt.
  • Powered by Markdoc