Billing
Subscriptions with Checkout

Create subscriptions with Checkout

Learn how to offer and manage subscriptions with different pricing options.

What you're building

This guide explains how to integrate Stripe’s hosted forms for managing subscription payments:

  • Checkout, for collecting payment and creating the subscription
  • The customer portal, for helping your customers manage their subscriptions

You can find code for an example implementation on GitHub.

Here’s what you’ll do:

  • Model a fixed-price subscription with Products and Prices. Checkout also supports metered billing and tiers.
  • Configure the customer portal
  • Collect payment information and create the subscription with Checkout
  • Integrate the customer portal to allow customers to manage their billing settings

This guide shows how to accept only card payments. You can add payment methods to Checkout, but the customer portal supports only cards. To handle payment updates for non-card payments, you can integrate the hosted invoice page, or work with Checkout in setup mode.

1 Set up Stripe

Install the Stripe client of your choice:

# Available as a gem gem install stripe
# If you use bundler, you can add this line to your Gemfile gem 'stripe'
# Install through pip pip install --upgrade stripe
# Or find the Stripe package on http://pypi.python.org/pypi/stripe/
# Find the version you want to pin: # https://github.com/stripe/stripe-python/blob/master/CHANGELOG.md # Specify that version in your requirements.txt file stripe>=2.48.0,<3.0
# Install the PHP library via Composer composer require stripe/stripe-php
# Or download the source directly: https://github.com/stripe/stripe-php/releases
/* For Gradle, add the following dependency to your build.gradle and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest */ implementation "com.stripe:stripe-java:{VERSION}"
<!-- For Maven, add the following dependency to your POM and replace {VERSION} with the version number you want to use from - https://mvnrepository.com/artifact/com.stripe/stripe-java or - https://github.com/stripe/stripe-java/releases/latest --> <dependency> <groupId>com.stripe</groupId> <artifactId>stripe-java</artifactId> <version>{VERSION}</version> </dependency>
# For other environments, manually install the following JARs: # - The Stripe JAR from https://github.com/stripe/stripe-java/releases/latest # - Google Gson from https://github.com/google/gson
# Install via npm npm install --save stripe
# Make sure your project is using Go Modules go mod init # Install stripe-go go get -u github.com/stripe/stripe-go/v71
// Then import the package import ( "github.com/stripe/stripe-go/v71" )
# Install via dotnet dotnet add package Stripe.net dotnet restore
# Or install via NuGet PM> Install-Package Stripe.net

And optionally install the Stripe CLI. The CLI provides webhook testing, and you can run it to create your products and prices.

To install the Stripe CLI with homebrew, run:

brew install stripe/stripe-cli/stripe

To install the Stripe CLI on macOS without homebrew:

  1. Download the latest mac-os tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
  2. Unzip the file: tar -xvf stripe_X.X.X_mac-os_x86_64.tar.gz

Optionally, install the binary in a location where you can execute it globally (e.g., /usr/local/bin).

To install the Stripe CLI on Debian and Ubuntu-based distributions:

  1. Add Bintray’s GPG key to the apt sources keyring:
sudo apt-key adv --keyserver hkp://pool.sks-keyservers.net:80 --recv-keys 379CE192D401AB61
  1. Add stripe-cli’s apt repository to the apt sources list:
echo "deb https://dl.bintray.com/stripe/stripe-cli-deb stable main" | sudo tee -a /etc/apt/sources.list
  1. Update the package list:
sudo apt-get update
  1. Install the CLI:
sudo apt-get install stripe

To install the Stripe CLI on RedHat and CentOS-based distributions:

  1. Add stripe-cli’s yum repository to the yum sources list:
wget https://bintray.com/stripe/stripe-cli-rpm/rpm -O bintray-stripe-stripe-cli-rpm.repo && sudo mv bintray-stripe-stripe-cli-rpm.repo /etc/yum.repos.d/
  1. Install the CLI:
sudo yum install stripe

To install the Stripe CLI on Linux without a package manager:

  1. Download the latest linux tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
  2. Unzip the file: tar -xvf stripe_X.X.X_linux_x86_64.tar.gz
  3. Run the executable: ./stripe

To install the Stripe CLI with Scoop, run:

scoop bucket add stripe https://github.com/stripe/scoop-stripe-cli.git
scoop install stripe

To install the Stripe CLI on Windows without Scoop, run:

  1. Download the latest windows tar.gz file from https://github.com/stripe/stripe-cli/releases/latest
  2. Unzip the stripe_X.X.X_windows_x86_64.zip file
  3. Run the unzipped .exe file

The Stripe CLI is also available as a Docker image. To install the latest version, run:

docker run --rm -it stripe/stripe-cli:latest

To run the Stripe CLI, you must also pair it with your Stripe account. Run stripe login and follow the prompts. For more information, see the Stripe CLI documentation page.

2 Create the business model Dashboard or Stripe CLI

You create your products and their pricing options in the Dashboard or with the Stripe CLI. A fixed-price service with two different options, named Basic and Premium, needs a product and a price for each option.

In this sample, each product bills at monthly intervals. The price for one product is 5 USD, and the other is 15 USD.

Navigate to the Create a product page, and create two products. Add one price for each product, each with a monthly billing interval:

  • Basic product
    • Price: 5.00 USD
  • Premium product
    • Price: 15.00 USD

After you create the prices, record the price IDs so they can be used in subsequent steps. Each ID is displayed in the Pricing section of the product and should look similar to this: price_G0FvDp6vZvdwRZ.

The Copy to live mode button at the top right of the page lets you clone your product from test mode to live mode when you’re ready.

Create the product objects:

# Premium product stripe products create \ --name="Billing Guide: Premium Service" \ --description="Premium service with extra features" # Basic product stripe products create \ --name="Billing Guide: Basic Service" \ --description="Basic service with minimum features"

Record the product ID for each product. They look like this:

{ "id": "prod_H94k5odtwJXMtQ",
See all 21 lines "object": "product", "active": true, "attributes": [ ], "created": 1587577341, "description": "Premium service with extra features", "images": [ ], "livemode": false, "metadata": { }, "name": "Billing Guide: Premium Service", "statement_descriptor": null, "type": "service", "unit_label": null, "updated": 1587577341 }

Use the product IDs to create a price for each product. Note that unit_amount is specified in cents, so 1500 = 15 USD, for example.

# Premium price stripe prices create \ -d product=prod_H94k5odtwJXMtQ \ -d unit_amount=1500 \ -d currency=usd \ -d "recurring[interval]"=month # Basic price stripe prices create \ -d product=prod_HGd6W1VUqqXGvr \ -d unit_amount=500 \ -d currency=usd \ -d "recurring[interval]"=month

Record the price ID for each price so they can be used in subsequent steps. They look like this:

{ "id": "price_HGd7M3DV3IMXkC",
See all 27 lines "object": "price", "product": "prod_HGd6W1VUqqXGvr", "type": "recurring", "currency": "usd", "recurring": { "aggregate_usage": null, "interval": "month", "interval_count": 1, "trial_period_days": null, "usage_type": "licensed" }, "active": true, "billing_scheme": "per_unit", "created": 1589319695, "livemode": false, "lookup_key": null, "metadata": { }, "nickname": null, "unit_amount": 1500, "unit_amount_decimal": "1500", "tiers": null, "tiers_mode": null, "transform_quantity": null }

For other business models, see Billing examples.

3 Configure the customer portal Dashboard

You configure the portal in the Dashboard. At a minimum, make sure to enable the functionality to allow customers to update their payment methods. See Integrating the customer portal for information about other settings you can configure.

4 Create a Checkout Session Server

On the backend of your application, define an endpoint that creates the session for your frontend to call. You need these values:

  • The price ID of the subscription the customer is signing up for; this value is passed from your frontend.
  • Your success_url, a page on your website that Checkout returns your customer to after they complete the payment
  • Your cancel_url, a page on your website that Checkout returns your customer to if they cancel the payment process
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/create-checkout-session' do content_type 'application/json' data = JSON.parse(request.body.read) # See https://stripe.com/docs/api/checkout/sessions/create # for additional parameters to pass. # {CHECKOUT_SESSION_ID} is a string literal; do not change it! # the actual Session ID is returned in the query parameter when your customer # is redirected to the success page. begin session = Stripe::Checkout::Session.create( success_url: 'https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}', cancel_url: 'https://example.com/canceled.html', payment_method_types: ['card'], mode: 'subscription', line_items: [{ # For metered billing, do not pass quantity quantity: 1, price: data['priceId'], }], ) rescue => e halt 400, { 'Content-Type' => 'application/json' }, { 'error': { message: e.error.message } }.to_json end { sessionId: session.id }.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/create-checkout-session', methods=['POST']) def create_checkout_session(): data = json.loads(request.data) try: # See https://stripe.com/docs/api/checkout/sessions/create # for additional parameters to pass. # {CHECKOUT_SESSION_ID} is a string literal; do not change it! # the actual Session ID is returned in the query parameter when your customer # is redirected to the success page. checkout_session = stripe.checkout.Session.create( success_url="https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}", cancel_url="https://example.com/canceled.html", payment_method_types=["card"], mode="subscription", line_items=[ { "price": data['priceId'], # For metered billing, do not pass quantity "quantity": 1 } ], ) return jsonify({'sessionId': checkout_session['id']}) except Exception as e: return jsonify({'error': {'message': str(e)}}), 400
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/create-checkout-session', function(Request $request, Response $response, array $args) { $body = json_decode($request->getBody()); try { // See https://stripe.com/docs/api/checkout/sessions/create // for additional parameters to pass. // {CHECKOUT_SESSION_ID} is a string literal; do not change it! // the actual Session ID is returned in the query parameter when your customer // is redirected to the success page. $checkout_session = \Stripe\Checkout\Session::create([ 'success_url' => 'https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}', 'cancel_url' => 'https://example.com/canceled.html', 'payment_method_types' => ['card'], 'mode' => 'subscription', 'line_items' => [[ 'price' => $body->priceId, // For metered billing, do not pass quantity 'quantity' => 1, ]], ]); } catch (Exception $e) { return $response->withJson([ 'error' => [ 'message' => $e->getError()->message, ], ], 400); } return $response->withJson(['sessionId' => $checkout_session['id']]); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/create-checkout-session", (request, response) -> { response.type("application/json"); CreateCheckoutSessionRequest req = gson.fromJson(request.body(), CreateCheckoutSessionRequest.class); // See https://stripe.com/docs/api/checkout/sessions/create // for additional parameters to pass. // {CHECKOUT_SESSION_ID} is a string literal; do not change it! // the actual Session ID is returned in the query parameter when your customer // is redirected to the success page. SessionCreateParams params = new SessionCreateParams.Builder() .setSuccessUrl("https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}") .setCancelUrl("https://example.com/canceled.html") .addPaymentMethodType(SessionCreateParams.PaymentMethodType.CARD) .setMode(SessionCreateParams.Mode.SUBSCRIPTION) .addLineItem(new SessionCreateParams.LineItem.Builder() // For metered billing, do not pass quantity .setQuantity(1L) .setPrice(req.getPriceId()) .build() ) .build(); try { Session session = Session.create(params); Map<String, Object> responseData = new HashMap<>(); responseData.put("sessionId", session.getId()); return gson.toJson(responseData); } catch(Exception e) { Map<String, Object> messageData = new HashMap<>(); messageData.put("message", e.getMessage()); Map<String, Object> responseData = new HashMap<>(); responseData.put("error", messageData); response.status(400); return gson.toJson(responseData); } });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post("/create-checkout-session", async (req, res) => { const { priceId } = req.body; // See https://stripe.com/docs/api/checkout/sessions/create // for additional parameters to pass. try { const session = await stripe.checkout.sessions.create({ mode: "subscription", payment_method_types: ["card"], line_items: [ { price: priceId, // For metered billing, do not pass quantity quantity: 1, }, ], // {CHECKOUT_SESSION_ID} is a string literal; do not change it! // the actual Session ID is returned in the query parameter when your customer // is redirected to the success page. success_url: 'https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}', cancel_url: 'https://example.com/canceled.html', }); res.send({ sessionId: session.id, }); } catch (e) { res.status(400); return res.send({ error: { message: e.message, } }); } });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" func handleCreateCheckoutSession(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { Price string `json:"priceId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } // See https://stripe.com/docs/api/checkout/sessions/create // for additional parameters to pass. // {CHECKOUT_SESSION_ID} is a string literal; do not change it! // the actual Session ID is returned in the query parameter when your customer // is redirected to the success page. params := &stripe.CheckoutSessionParams{ SuccessURL: "https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}", CancelURL: "https://example.com/canceled.html", PaymentMethodTypes: stripe.StringSlice([]string{ "card", }), Mode: stripe.String(string(stripe.CheckoutSessionModeSubscription)), LineItems: []*stripe.CheckoutSessionLineItemParams{ &stripe.CheckoutSessionLineItemParams{ Price: stripe.String(req.Price), // For metered billing, do not pass quantity Quantity: stripe.Int64(1), }, }, } s, err := session.New(params) if err != nil { w.WriteHeader(http.StatusBadRequest) writeJSON(w, struct { ErrorData string `json:"error"` }{ ErrorData: "test", }) return } writeJSON(w, struct { SessionID string `json:"sessionId"` }{ SessionID: s.ID, }); }
using Newtonsoft.Json; public class CreateCheckoutSessionRequest { [JsonProperty("priceId")] public string PriceId { get; set; } }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("create-checkout-session")] public async Task<IActionResult> CreateCheckoutSession([FromBody] CreateCheckoutSessionRequest req) { var options = new SessionCreateOptions { // See https://stripe.com/docs/api/checkout/sessions/create // for additional parameters to pass. // {CHECKOUT_SESSION_ID} is a string literal; do not change it! // the actual Session ID is returned in the query parameter when your customer // is redirected to the success page. SuccessUrl = "https://example.com/success.html?session_id={CHECKOUT_SESSION_ID}", CancelUrl = "https://example.com/canceled.html", PaymentMethodTypes = new List<string> { "card", }, Mode = "subscription", LineItems = new List<SessionLineItemOptions> { new SessionLineItemOptions { Price = req.PriceId, // For metered billing, do not pass quantity Quantity = 1, }, }, }; var service = new SessionService(this.client); try { var session = await service.CreateAsync(options); return Ok(new CreateCheckoutSessionResponse { SessionId = session.Id, }); } catch (StripeException e) { Console.WriteLine(e.StripeError.Message); return BadRequest(new ErrorResponse { ErrorMessage = new ErrorMessage { Message = e.StripeError.Message, } }); } }

The response object includes an id value you need in subsequent calls to the Checkout Sessions endpoint.

In this example, the success_url is customized by appending the Session ID. For more information about this approach, see the documentation on how to Customize your success page.

5 Send customers to the Checkout form Client

On the frontend of your application, add a button that takes your customer to Checkout to complete their payment. You also need to include Stripe.js:

<head> <title>Checkout</title> <script src="https://js.stripe.com/v3/"></script> </head> <body> <button id="checkout">Subscribe</button> </body>

Pass the price ID of your customer’s selection to the backend endpoint that creates the Checkout Session:

var createCheckoutSession = function(priceId) { return fetch("/create-checkout-session", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ priceId: priceId }) }).then(function(result) { return result.json(); }); };

Add an event handler to the button to redirect to Checkout, calling redirectToCheckout and passing the Checkout Session ID:

document .getElementById("checkout") .addEventListener("click", function(evt) { createCheckoutSession(PriceId).then(function(data) { // Call Stripe.js method to redirect to the new Checkout page stripe .redirectToCheckout({ sessionId: data.sessionId }) .then(handleResult); }); });

After the subscription signup succeeds, the customer is returned to your website at the success_url and a checkout.session.completed event is sent. You can check for this event in the Dashboard or with a webhook endpoint and the Stripe CLI.

6 Send customers to the Billing portal Client

On your frontend, add a button to the page at the success_url that provides a link to the customer portal:

<head> <script src="./success.js" defer></script> </head> <body> <form id="manage-billing-form"> <button>Manage Billing</button> </form> </body>

Pass the Checkout Session ID to a backend endpoint that retrieves the Checkout Session and gets the customer ID from the response:

const urlParams = new URLSearchParams(window.location.search); const sessionId = urlParams.get("session_id") let customerId; if (sessionId) { fetch("/checkout-session?sessionId=" + sessionId) .then(function(result){ return result.json() }) .then(function(session){ // We store the customer ID here so that we can pass to the // server and redirect to customer portal. Note that, in practice // this ID should be stored in your database when you receive // the checkout.session.completed event. This demo does not have // a database, so this is the workaround. This is *not* secure. // You should use the Stripe Customer ID from the authenticated // user on the server. customerId = session.customer; var sessionJSON = JSON.stringify(session, null, 2); document.querySelector("pre").textContent = sessionJSON; }) .catch(function(err){ console.log('Error when fetching Checkout session', err); });

Add an event handler to the button to redirect to the portal, passing the customer ID:

// In production, this should check CSRF, and not pass the session ID. // The customer ID for the portal should be pulled from the // authenticated user on the server. const manageBillingForm = document.querySelector('#manage-billing-form'); manageBillingForm.addEventListener('submit', function(e) { e.preventDefault(); fetch('/customer-portal', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ sessionId: sessionId }), }) .then((response) => response.json()) .then((data) => { window.location.href = data.url; }) .catch((error) => { console.error('Error:', error); }); });

7 Create a portal Session Server

On the backend, define the endpoint that retrieves the Checkout session for your frontend to call. The response object includes the customer ID that you pass to the portal session.

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' get '/checkout-session' do content_type 'application/json' session_id = params[:sessionId] session = Stripe::Checkout::Session.retrieve(session_id) session.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/checkout-session', methods=['GET']) def get_checkout_session(): id = request.args.get('sessionId') checkout_session = stripe.checkout.Session.retrieve(id) return jsonify(checkout_session)
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->get('/checkout-session', function (Request $request, Response $response, array $args) { $id = $request->getQueryParams()['sessionId']; $checkout_session = \Stripe\Checkout\Session::retrieve($id); return $response->withJson($checkout_session); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; get("/checkout-session", (request, response) -> { response.type("application/json"); String sessionId = request.queryParams("sessionId"); Session session = Session.retrieve(sessionId); return gson.toJson(session); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.get("/checkout-session", async (req, res) => { const { sessionId } = req.query; const session = await stripe.checkout.sessions.retrieve(sessionId); res.send(session); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" func handleCheckoutSession(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } sessionID := r.URL.Query().Get("sessionId") s, _ := session.Get(sessionID, nil) writeJSON(w, s) }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpGet("checkout-session")] public async Task<IActionResult> CheckoutSession(string sessionId) { var service = new SessionService(this.client); var session = await service.GetAsync(sessionId); return Ok(session); }

Define the endpoint that creates the customer portal session for your frontend to call, passing the customer ID from the frontend. You can also pass an optional return_url value for the page on your site where your customer is redirected after they finish managing their subscription:

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/customer-portal' do content_type 'application/json' data = JSON.parse(request.body.read) # For demonstration purposes, we're using the Checkout session to retrieve the customer ID. # Typically this is stored alongside the authenticated user in your database. checkout_session_id = data['sessionId'] checkout_session = Stripe::Checkout::Session.retrieve(checkout_session_id) # This is the URL to which users will be redirected after they are done # managing their billing. return_url = ENV['DOMAIN'] session = Stripe::BillingPortal::Session.create({ customer: checkout_session['customer'], return_url: return_url }) { url: session.url }.to_json end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/customer-portal', methods=['POST']) def customer_portal(): data = json.loads(request.data) # For demonstration purposes, we're using the Checkout session to retrieve the customer ID. # Typically this is stored alongside the authenticated user in your database. checkout_session_id = data['sessionId'] checkout_session = stripe.checkout.Session.retrieve(checkout_session_id) # This is the URL to which the customer will be redirected after they are # done managing their billing with the portal. return_url = os.getenv("DOMAIN") session = stripe.billing_portal.Session.create( customer=checkout_session.customer, return_url=return_url) return jsonify({'url': session.url})
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/customer-portal', function(Request $request, Response $response) { $body = json_decode($request->getBody()); // For demonstration purposes, we're using the Checkout session to retrieve the customer ID. // Typically this is stored alongside the authenticated user in your database. $checkout_session = \Stripe\Checkout\Session::retrieve($body->sessionId); $stripe_customer_id = $checkout_session->customer; // This is the URL to which the user will be redirected after they have // finished managing their billing in the portal. $return_url = getenv('DOMAIN'); $session = \Stripe\BillingPortal\Session::create([ 'customer' => $stripe_customer_id, 'return_url' => $return_url, ]); return $response->withJson(['url' => $session->url]); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/customer-portal", (request, response) -> { response.type("application/json"); // For demonstration purposes, we're using the Checkout session to retrieve the customer ID. // Typically this is stored alongside the authenticated user in your database. CreateCustomerPortalSessionRequest req = gson.fromJson(request.body(), CreateCustomerPortalSessionRequest.class); Session checkoutsession = Session.retrieve(req.getSessionId()); String customer = checkoutsession.getCustomer(); String domainUrl = dotenv.get("DOMAIN"); com.stripe.param.billingportal.SessionCreateParams params = new com.stripe.param.billingportal.SessionCreateParams.Builder() .setReturnUrl(domainUrl) .setCustomer(customer) .build(); com.stripe.model.billingportal.Session portalsession = com.stripe.model.billingportal.Session.create(params); Map<String, Object> responseData = new HashMap<>(); responseData.put("url", portalsession.getUrl()); return gson.toJson(responseData); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post('/customer-portal', async (req, res) => { // For demonstration purposes, we're using the Checkout session to retrieve the customer ID. // Typically this is stored alongside the authenticated user in your database. const { sessionId } = req.body; const checkoutsession = await stripe.checkout.sessions.retrieve(sessionId); // This is the url to which the customer will be redirected when they are done // managign their billing with the portal. const returnUrl = process.env.DOMAIN; const portalsession = await stripe.billingPortal.sessions.create({ customer: checkoutsession.customer, return_url: returnUrl, }); res.send({ url: portalsession.url, }); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" func handleCustomerPortal(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } var req struct { SessionID string `json:"sessionId"` } if err := json.NewDecoder(r.Body).Decode(&req); err != nil { http.Error(w, err.Error(), http.StatusInternalServerError) log.Printf("json.NewDecoder.Decode: %v", err) return } // For demonstration purposes, we're using the Checkout session to retrieve the customer ID. // Typically this is stored alongside the authenticated user in your database. sessionID := req.SessionID s, _ := session.Get(sessionID, nil) // The URL to which the user is redirected when they are done managing // billing in the portal. returnURL := os.Getenv("DOMAIN") params := &stripe.BillingPortalSessionParams{ Customer: stripe.String(s.Customer.ID), ReturnURL: stripe.String(returnURL), } ps, _ := portalsession.New(params) writeJSON(w, struct { URL string `json:"url"` }{ URL: ps.URL, }) }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("customer-portal")] public async Task<IActionResult> CustomerPortal([FromBody] CustomerPortalRequest req) { // For demonstration purposes, we're using the Checkout session to retrieve the customer ID. // Typically this is stored alongside the authenticated user in your database. var checkoutSessionId = req.SessionId; var checkoutService = new SessionService(this.client); var checkoutSession = await checkoutService.GetAsync(checkoutSessionId); // This is the URL to which your customer will return after // they are done managing billing in the Customer Portal. var returnUrl = this.options.Value.Domain; var options = new Stripe.BillingPortal.SessionCreateOptions { Customer = checkoutSession.CustomerId, ReturnUrl = returnUrl, }; var service = new Stripe.BillingPortal.SessionService(this.client); var session = await service.CreateAsync(options); return Ok(session); }

8 Provision and monitor subscriptions Server

When your customer completes the Checkout form and you receive a checkout.session.completed event, you should provision the subscription. Continue to provision each month (for the case of this sample, which bills monthly) as you receive invoice.paid events. If you receive an invoice.payment_failed event, notify your customer and send them to the customer portal to update their payment method.

For testing purposes, you can monitor events in the Dashboard, but for production you should set up a webhook endpoint and subscribe to appropriate event types:

# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' post '/webhook' do webhook_secret = {{'STRIPE_WEBHOOK_SECRET'}} payload = request.body.read if !webhook_secret.empty? # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. sig_header = request.env['HTTP_STRIPE_SIGNATURE'] event = nil begin event = Stripe::Webhook.construct_event( payload, sig_header, webhook_secret ) rescue JSON::ParserError => e # Invalid payload status 400 return rescue Stripe::SignatureVerificationError => e # Invalid signature puts '⚠️ Webhook signature verification failed.' status 400 return end else data = JSON.parse(payload, symbolize_names: true) event = Stripe::Event.construct_from(data) end # Get the type of webhook event sent event_type = event['type'] data = event['data'] data_object = data['object'] case event.type when 'checkout.session.completed' # Payment is successful and the subscription is created. # You should provision the subscription. when 'invoice.paid' # Continue to provision the subscription as payments continue to be made. # Store the status in your database and check when a user accesses your service. # This approach helps you avoid hitting rate limits. when 'invoice.payment_failed' # The payment failed or the customer does not have a valid payment method. # The subscription becomes past_due. Notify your customer and send them to the # customer portal to update their payment information. else puts "Unhandled event type: #{event.type}" end status 200 end
# Set your secret key. Remember to switch to your live secret key in production! # See your keys here: https://dashboard.stripe.com/account/apikeys stripe.api_key = 'sk_test_4eC39HqLyjWDarjtT1zdp7dc' @app.route('/webhook', methods=['POST']) def webhook_received(): webhook_secret = {{'STRIPE_WEBHOOK_SECRET'}} request_data = json.loads(request.data) if webhook_secret: # Retrieve the event by verifying the signature using the raw body and secret if webhook signing is configured. signature = request.headers.get('stripe-signature') try: event = stripe.Webhook.construct_event( payload=request.data, sig_header=signature, secret=webhook_secret) data = event['data'] except Exception as e: return e # Get the type of webhook event sent - used to check the status of PaymentIntents. event_type = event['type'] else: data = request_data['data'] event_type = request_data['type'] data_object = data['object'] if event_type == 'checkout.session.completed' # Payment is successful and the subscription is created. # You should provision the subscription. print(data) elif event_type == 'invoice.paid' # Continue to provision the subscription as payments continue to be made. # Store the status in your database and check when a user accesses your service. # This approach helps you avoid hitting rate limits. print(data) elif event_type == 'invoice.payment_failed' # The payment failed or the customer does not have a valid payment method. # The subscription becomes past_due. Notify your customer and send them to the # customer portal to update their payment information. print(data) else: print('Unhandled event type {}'.format(event.type)) return jsonify({'status': 'success'})
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys \Stripe\Stripe::setApiKey('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); $app->post('/webhook', function(Request $request, Response $response) { $logger = $this->get('logger'); $event = $request->getParsedBody(); // Parse the message body and check the signature $webhookSecret = {{'STRIPE_WEBHOOK_SECRET'}}; if ($webhookSecret) { try { $event = \Stripe\Webhook::constructEvent( $request->getBody(), $request->getHeaderLine('stripe-signature'), $webhookSecret ); } catch (\Exception $e) { return $response->withJson([ 'error' => $e->getMessage() ])->withStatus(403); } } else { $event = $request->getParsedBody(); } $type = $event['type']; $object = $event['data']['object']; switch ($type) { case 'checkout.session.completed': // Payment is successful and the subscription is created. // You should provision the subscription. break; case 'invoice.paid': // Continue to provision the subscription as payments continue to be made. // Store the status in your database and check when a user accesses your service. // This approach helps you avoid hitting rate limits. break; case 'invoice.payment_failed': // The payment failed or the customer does not have a valid payment method. // The subscription becomes past_due. Notify your customer and send them to the // customer portal to update their payment information. break; // ... handle other event types default: // Unhandled event type } return $response->withJson([ 'status' => 'success' ])->withStatus(200); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys Stripe.apiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; post("/webhook", (request, response) -> { String payload = request.body(); String sigHeader = request.headers("Stripe-Signature"); String endpointSecret = {{'STRIPE_WEBHOOK_SECRET'}}; Event event = null; try { event = Webhook.constructEvent(payload, sigHeader, endpointSecret); } catch (SignatureVerificationException e) { // Invalid signature response.status(400); return ""; } switch (event.getType()) { case "checkout.session.completed": // Payment is successful and the subscription is created. // You should provision the subscription. break; case "invoice.paid": // Continue to provision the subscription as payments continue to be made. // Store the status in your database and check when a user accesses your service. // This approach helps you avoid hitting rate limits. break; case "invoice.payment_failed": // The payment failed or the customer does not have a valid payment method. // The subscription becomes past_due. Notify your customer and send them to the // customer portal to update their payment information. break; default: // System.out.println("Unhandled event type: " + event.getType()); } response.status(200); return ""; } );
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys const stripe = require('stripe')('sk_test_4eC39HqLyjWDarjtT1zdp7dc'); app.post("/webhook", async (req, res) => { let eventType; // Check if webhook signing is configured. const webhookSecret = {{'STRIPE_WEBHOOK_SECRET'}} if (webhookSecret) { // Retrieve the event by verifying the signature using the raw body and secret. let event; let signature = req.headers["stripe-signature"]; try { event = stripe.webhooks.constructEvent( req.rawBody, signature, process.env.STRIPE_WEBHOOK_SECRET ); } catch (err) { console.log(`⚠️ Webhook signature verification failed.`); return res.sendStatus(400); } // Extract the object from the event. data = event.data; eventType = event.type; } else { // Webhook signing is recommended, but if the secret is not configured in `config.js`, // retrieve the event data directly from the request body. data = req.body.data; eventType = req.body.type; } switch (event.type) { case 'checkout.session.completed': // Payment is successful and the subscription is created. // You should provision the subscription. break; case 'invoice.paid': // Continue to provision the subscription as payments continue to be made. // Store the status in your database and check when a user accesses your service. // This approach helps you avoid hitting rate limits. break; case 'invoice.payment_failed': // The payment failed or the customer does not have a valid payment method. // The subscription becomes past_due. Notify your customer and send them to the // customer portal to update their payment information. break; default: // Unhandled event type } res.sendStatus(200); });
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys stripe.Key = "sk_test_4eC39HqLyjWDarjtT1zdp7dc" func handleWebhook(w http.ResponseWriter, r *http.Request) { if r.Method != "POST" { http.Error(w, http.StatusText(http.StatusMethodNotAllowed), http.StatusMethodNotAllowed) return } b, err := ioutil.ReadAll(r.Body) if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("ioutil.ReadAll: %v", err) return } event, err := webhook.ConstructEvent(b, r.Header.Get("Stripe-Signature"), {{'STRIPE_WEBHOOK_SECRET'}} if err != nil { http.Error(w, err.Error(), http.StatusBadRequest) log.Printf("webhook.ConstructEvent: %v", err) return } switch event.Type { case "checkout.session.completed": // Payment is successful and the subscription is created. // You should provision the subscription. case 'invoice.paid': // Continue to provision the subscription as payments continue to be made. // Store the status in your database and check when a user accesses your service. // This approach helps you avoid hitting rate limits. case 'invoice.payment_failed': // The payment failed or the customer does not have a valid payment method. // The subscription becomes past_due. Notify your customer and send them to the // customer portal to update their payment information. default: // unhandled event type } }
// Set your secret key. Remember to switch to your live secret key in production! // See your keys here: https://dashboard.stripe.com/account/apikeys StripeConfiguration.ApiKey = "sk_test_4eC39HqLyjWDarjtT1zdp7dc"; [HttpPost("webhook")] public async Task<IActionResult> Webhook() { var json = await new StreamReader(HttpContext.Request.Body).ReadToEndAsync(); Event stripeEvent; try { var webhookSecret = {{'STRIPE_WEBHOOK_SECRET'}} stripeEvent = EventUtility.ConstructEvent( json, Request.Headers["Stripe-Signature"], webhookSecret ); Console.WriteLine($"Webhook notification with type: {stripeEvent.Type} found for {stripeEvent.Id}"); } catch (Exception e) { Console.WriteLine($"Something failed {e}"); return BadRequest(); } switch (stripeEvent.Type) { case 'checkout.session.completed': // Payment is successful and the subscription is created. // You should provision the subscription. break; case 'invoice.paid': // Continue to provision the subscription as payments continue to be made. // Store the status in your database and check when a user accesses your service. // This approach helps you avoid hitting rate limits. break; case 'invoice.payment_failed': // The payment failed or the customer does not have a valid payment method. // The subscription becomes past_due. Notify your customer and send them to the // customer portal to update their payment information. break; default: // Unhandled event type } return Ok(); }

The minimum event types to monitor:

Event name Description
checkout.session.completed Sent when a customer clicks the Pay or Subscribe button in Checkout, informing you of a new purchase.
invoice.paid Sent each billing interval when a payment succeeds.
invoice.payment_failed Sent each billing interval if there is an issue with your customer’s payment method.

If you configure the customer portal to allow more actions, such as canceling a subscription, see Integrating the customer portal for events to monitor.

For even more events to monitor, see Subscription webhooks.