Build a subscription integration

This guide describes how to sell a fixed-price monthly subscription offering using Flex

At this point you should have a flex account, access to the partner dashboard, and have gone through the onboarding process of adding your payout information with Stripe.

Below we walk through a full example of how to integrate Flex Checkout into your checkout flow.

Create an API Key in the partner dashboard

In the partner dashboard, navigate to the Developers section. Here we will find an API Keys tab where you can create an API Key to interact with Flex.

Select the New API Key button and give your API Key a name. With this key we can now interact with the Flex API.

Define a product to sell

Always keep sensitive information about your product inventory, such as price and availability, on your server to prevent customer manipulation from the client. Define product information when you create the Checkout Session using predefined price IDs or on the fly with price_data.

For this example we've created a product that is eligible via a letter of medical necessity. To learn more about creating products and their eligibility review the Product docs or Product API.

curl --request POST \
  --url \
  --header 'authorization: Bearer fsk_MzkxYzU2MTAtZGYxOC00OWUyLThlODQtYTA0ODMwNjhiYmVm' \
  --header 'content-type: application/json' \
  --data '{"product": {"name": "Test Product","description": "Test Product Description","visit_type": "gym","hsa_fsa_eligibility": "letter_of_medical_necessity"}}'

The response we get back is:

  "product": {
    "product_id": "fprod_01HW5MXAPBE79RHMMJJGB4ACAB",
    "name": "Test Product",
    "description": "Test Product Description",
    "created_at": "2024-04-23T14:14:16.029253Z",
    "visit_type": "gym",
    "active": true,
    "upc_code": null,
    "gtin": null,
    "hsa_fsa_eligibility": "letter_of_medical_necessity"

Define the pricing model

Given the product_id that was returned to in the previous payload we'll define a pricing model where we'll charge $49.99 every month

curl --request POST \
  --url \
  --header 'authorization: Bearer fsk_MzkxYzU2MTAtZGYxOC00OWUyLThlODQtYTA0ODMwNjhiYmVm' \
  --header 'content-type: application/json' \
  --data '{"price": {"product": "fprod_01HW5MXAPBE79RHMMJJGB4ACAB","description": "Our awesome new price","unit_amount": 4999,"recurring": {"interval": "monthly"}}}'

The response we get back is:

  "price": {
    "price_id": "fprice_01HWDV9J9JWY5SDFJZNFXN5S6P",
    "description": "Our awesome new price",
    "unit_amount": 4999,
    "price": 4999,
    "recurring": {
      "interval": "monthly",
      "trial_period_days": null
    "active": true,
    "product": "fprod_01HW5MXAPBE79RHMMJJGB4ACAB",
    "created_at": "2024-04-26T18:39:43.93664Z",
    "type": "recurring",
    "metadata": null

Supply success and cancel URLs

Specify URLs for success and cancel pages—make sure they’re publicly accessible so Flex can redirect customers to them. You can also handle both the success and canceled states with the same URL.

success_url: cancel_url:

Build your checkout

On your site you should have an order preview page. On the order preview page you will add a button that will trigger your server to generate a checkout session.

Generate a checkout session

On the server you will generate a checkout session.

curl --request POST \
  --url \
  --header 'authorization: Bearer fsk_MzkxYzU2MTAtZGYxOC00OWUyLThlODQtYTA0ODMwNjhiYmVm' \
  --header 'content-type: application/json' \
  --data '{"checkout_session": {"line_items": [{"price": "fprice_01HWDV9J9JWY5SDFJZNFXN5S6P","quantity": 1}],"success_url": "","mode": "subscription","cancel_url": ""}}'

Redirect users to checkout

The response we got back looks like:

  "checkout_session": {
    "allow_promotion_codes": false,
    "amount_total": 4999,
    "amount_subtotal": 4999,
    "cancel_url": "",
    "capture_method": "automatic",
    "checkout_session_id": "fcs_01HWDVDMWQT1N8TJJYDWKW4ZHZ",
    "created_at": 1714156917,
    "customer": null,
    "customer_email": null,
    "defaults": {
      "email": null,
      "first_name": null,
      "last_name": null,
      "phone": null
    "expires_at": 1714160517,
    "invoice": null,
    "hsa_fsa_eligible": true,
    "letter_of_medical_necessity_required": true,
    "metadata": null,
    "mode": "subscription",
    "multiple_subscriptions": false,
    "payment_intent_id": null,
    "redirect_url": "http://localhost:3000/pay/c/fcs_01HWDVDMWQT1N8TJJYDWKW4ZHZ",
    "setup_intent": null,
    "shipping_options": null,
    "shipping_address_collection": false,
    "shipping_details": null,
    "status": "open",
    "success_url": "",
    "subscription": null,
    "tax_rate": null,
    "test_mode": false,
    "total_details": {
      "amount_discount": 0,
      "amount_tax": null,
      "amount_shipping": null
    "visit_type": "gym"

Provision and monitor subscriptions

After the subscription signup succeeds, the customer returns to the success_url specified, which initiates a checkout.session.completed event. When you receive a checkout.session.completed event, you can provision the subscription. Continue to provision for each billinging cycle as you receive invoice.paid events. If you receive an invoice.payment_failed event, notify your customer and perform the appropriate action your team has decided at this moment.

To determine the next step for your system’s logic, check the event type and parse the payload of each event, such as invoice.paid. Store the subscription_id and customer_id event objects in your database for verification.

Try it out

Navigate to the redirect_url and use any of these tests cards to simulate a payment.

Payment succeeds: 4242424242424242 Payment is decline: 4000000000009995

If you have setup your webhooks, you should see the checkout.session.completed upon a successful payment.


You have a basic subscriptions integration working!

Next Steps

Explore checkout sesession scenarios

Depending on your setup/business you may want to rely on additional parameters that are avaialable as part of the Checkout Session API. Some of the common scenarios, can be found here.