# Metered Usage

## Before you start

Creating a metered usage subscription is similar to:

{% content-ref url="adding-a-subscription" %}
[adding-a-subscription](https://docs.petal.build/petal-pro-documentation/guides/adding-a-subscription)
{% endcontent-ref %}

The main differences being:

* In Stripe you create a Meter and you create Products/Prices that are metered
* In Petal Pro you record and synchronise usage with Stripe

The following will guide you through the process of adding metered usage to the AI chat feature.

## Starting with a fresh app <a href="#starting-with-a-fresh-app" id="starting-with-a-fresh-app"></a>

For this tutorial you will need:

* Access to Petal Pro (you can purchase it on [petal.build](https://petal.build/))
* Elixir & Erlang installed (you can follow our [installation guide](https://docs.petal.build/petal-pro-documentation/fundamentals/installation) to get up and running)
* PostgreSQL running: [Postgres.app](https://postgresapp.com/) works well for Mac or we have a docker-compose.yaml file that will run Postgres for you
* An account on [Stripe](https://stripe.com/)
* For deployment (optional) you will need:
  * An account on [Fly.io](https://fly.io/)

### Download <a href="#download" id="download"></a>

Head over to the [projects page](https://petal.build/pro/projects) and create a new project, then download v3.0.0. You can unzip it in your terminal with the command `unzip petal_pro_3.0.0.zip`.

### Run <a href="#run" id="run"></a>

First, rename the project:

{% code title="Terminal" %}

```sh
mv petal_pro_3.0.0 sub_demo
cd sub_demo
mix rename PetalPro SubDemo
```

{% endcode %}

Next, start the demo app:

{% code title="Terminal" %}

```sh
mix setup
mix phx.server
```

{% endcode %}

## Configure Stripe

Before we do anything inside the demo app, we need to add some [Stripe](https://stripe.com/) Products. Login using your account.  Make sure that Stripe is in "test mode" - this will allow you to test purchasing without a credit card.

{% hint style="info" %}
Stripe has introduced isolated test environments through [Sandboxes](https://docs.stripe.com/sandboxes). We recommend using a Sandbox in place of "test mode".
{% endhint %}

### Install the Stripe CLI

Follow these [directions](https://stripe.com/docs/stripe-cli#install) to install the Stripe CLI. Then login via the command line (the instructions to login are also on this page):

{% code title="Terminal" %}

```bash
stripe login
```

{% endcode %}

{% hint style="info" %}
Commands run using the Stripe CLI default to test mode
{% endhint %}

### Create a product with usage-based prices

First create the "Essential" product:

{% code title="Terminal" %}

```bash
stripe products create --name="Essential"
```

{% endcode %}

Take note of the `id` returned, it will look like `prod_xxxxx`.

Create the Monthly price for metered usage:

{% code title="Terminal" %}

```sh
stripe prices create \
  --unit-amount=5 \
  --currency=usd \
  -d "transform_quantity[divide_by]"=1000 \
  -d "transform_quantity[round]"=up \
  -d "recurring[interval]"=month \
  -d "recurring[usage_type]"=metered \
  --product="prod_xxxxx"
```

{% endcode %}

Make sure you replace `prod_xxxxx` with the `id` returned from the previous call. `usage_type` is set to "metered" - which means that the unit price isn't fixed. The price is 5 cents per 1000 units (rounding up - meaning a minimum of $0.05 will be charged on single use).

{% hint style="info" %}
Note that `--unit-amount` is in cents, rather than dollars
{% endhint %}

### Add the "Business" and "Enterprise" products

In this guide we'll stick to per month pricing to keep it simple for the end user.

Repeat the process (above) to create a "Business" and an "Enterprise" product. Use the table below as a point of reference for suggested prices:

| Product       | Essential            | Business              | Enterprise           |
| ------------- | -------------------- | --------------------- | -------------------- |
| Monthly price | $0.05 per 1000 units | $0.5 per 15,000 units | $5 per 200,000 units |

### Enable switching plans for the Customer Portal

When purchasing a subscription, Petal Pro will redirect to Stripe. In the case where the user switches from an existing plan to another - you need to enable some settings for the Customer Portal. Once logged into Stripe:

1. Make sure Stripe is in Sandbox or Test mode
2. Click on the cog on the top right (`Settings`)
3. In the section titled "Billing", click on `Customer Portal`
4. Expand the "Subscriptions" section
5. Enable the setting, `Customers can switch plans`
6. Using "Find a test product..." add prices from the Essential, Business and Enterprise products

{% hint style="info" %}
If you can't add usage meter prices - you may have to remove all prices and then try again
{% endhint %}

In addition, you may want to review the "Business information" section - to customise the output of the Customer Portal.

### Create a Meter

The final step is to create a Meter. A Meter allows you to record usage against a particular Subscription/Price. Think of it as a way of grouping cost associated with a feature. In this guide we're going to create a Meter for the AI chat feature:

{% code title="Terminal" %}

```sh
stripe billing meters create  \
  --display-name="AI tokens" \
  --event-name=ai_tokens \
  -d "default_aggregation[formula]"=sum
```

{% endcode %}

{% hint style="info" %}
At the time of writing, `billing` is a recent addition to the Stripe CLI. Make sure that you're on the latest version of the Stripe CLI&#x20;
{% endhint %}

## Configure Petal Pro

Petal Pro comes pre-configured for Stripe integration. There are three things you need to do to test the integration locally:

* Run the CLI web hook
* Add Stripe settings to your development environment
* Update the Petal Pro config with product/price details (created above) from your Stripe account

### Run the CLI web hook

In order to test your Stripe integration on your dev machine, you'll need to run a web hook. This is done using the Stripe CLI (see the [Install the Stripe CLI](#install-the-stripe-cli) section for more information).

Once you are logged in via the Stripe CLI, you can run the following command:

{% code title="Terminal" overflow="wrap" %}

```bash
stripe listen --forward-to localhost:4000/webhooks/stripe
> Ready! You are using Stripe API Version [2022-11-15]. Your webhook signing secret is whsec_xxxxxxxxxxxxxxx (^C to quit)
```

{% endcode %}

Take note of the `whsec_xxxxxxxxxxxxxxx` secret. This will be used as your `STRIPE_WEBHOOK_SECRET` in the next section.

### Add Stripe settings to your development environment

First, you'll need your `STRIPE_SECRET` and your `STRIPE_WEBHOOK_SECRET`. To get your `STRIPE_SECRET`:

1. Go to the Stripe console
2. Make sure you're in Sandbox or Test mode
3. At the bottom left of the screen, click on `Developers` then click `API keys`
4. Under "Standard Keys", look for the "Secret key"
5. Click the secret key to copy it

To get the `STRIPE_WEBHOOK_SECRET`, see the instructions under [Run the CLI web hook](#run-the-cli-web-hook).

The next step is to add these as environment variables to your demo app. Make sure that you install [direnv](https://direnv.net/) and that you're in the root of the demo project:

{% code title="Terminal" %}

```bash
cp .envrc.example .envrc
```

{% endcode %}

Uncomment these lines at the bottom of the file:

{% code title=".envrc" %}

```bash
# If using Stripe for payments:
export STRIPE_SECRET=""
export STRIPE_WEBHOOK_SECRET=""
export STRIPE_PRODUCTION_MODE="false"
```

{% endcode %}

Update the values you obtained (above) for `STRIPE_SECRET` and `STRIPE_WEBHOOK_SECRET`.&#x20;

The `.envrc` file is listed in `.gitignore`. Meaning that the secrets will live on your development machine and will not be accidentally pushed to the `git` repo.

To activate the new environment variables:

{% code title="Terminal" %}

```bash
direnv allow
```

{% endcode %}

### Update the Product config

The following shows how you can update the Essential plan:

{% code title="config\config.exs" %}

```diff
config :petal_pro, :billing_products, [
  %{
    id: "essential",
    name: "Essential",
    description: "Essential description",
    most_popular: true,
    features: [
      "Essential feature 1",
      "Essential feature 2",
      "Essential feature 3"
    ],
    plans: [
      %{
        id: "essential-monthly",
        name: "Monthly",
-        amount: 1900,
+        unit_amount: 5,
+        per_units: 1000,
        interval: :month,
        allow_promotion_codes: true,
-        trial_days: 7,
        items: [
-          %{price: "price_1NLhPDIWVkWpNCp7trePDpmi", quantity: 1}
+          %{price: "price_xxxxx"}
        ]
      },
-      %{
-        id: "essential-yearly",
-        name: "Yearly",
-        amount: 19_900,
-        interval: :year,
-        allow_promotion_codes: true,
-        items: [
-          %{price: "price_1NWBYjIWVkWpNCp7pw4GpjI6", quantity: 1}
-        ]
-      }
    ]
  }, 
  # Business and Enterprise config too
}
```

{% endcode %}

`amount` has been replaced with `unit_amount` and `per_units` - this controls what the user sees for each plan on the subscribe page. Eliminating `trial_days` means that you don't have to wait until the trial is over to see upcoming charges.

In this guide we're focusing on monthly pricing - the `essential-yearly` product has been removed.&#x20;

When the user selects a plan, Petal Pro will use these settings to make a Stripe call. In the line where we're adding `price_xxxxx` we're omitting `quantity`. If the `quantity` were present it would be passed up too. In the case of a usage-based price, passing up `quantity` will result in a Stripe error.

Replace `price_xxxxx` with a valid price id. To get the list of prices for the "Essential" product:

{% code title="Terminal" %}

```bash
# To get the "Essential" Product id
stripe products search \
  --query="name:'Essential'"

# To list all Prices that belong to the "Essential" product
stripe prices list  \
  --product="essential_product_id" \
  --active=true
```

{% endcode %}

Repeat the above process for the Business and Enterprise plans.

### Update the Meter config

Add a reference to our "AI Tokens" meter:

{% code title="config/config.exs" %}

```diff
config :petal_pro, :billing_meters, [
-  # %{
-  #   id: "mtr_test_61SNiSGOruDKkjzQ241IWVkWpNCp70L2",
-  #   name: "API Usage",
-  #   event_name: "api_meter"
-  # }
+  %{
+    id: "mtr_xxx_xxxxx",
+    name: "AI Tokens",
+    event_name: "ai_tokens"
+  }
]
```

{% endcode %}

To get the current list of meters:

{% code title="Terminal" %}

```bash
stripe billing meters list
```

{% endcode %}

Find the meter that has the `event_name` of `ai_tokens`. Use it's `id` to replace `mtr_xxx_xxxxx`.

### Enable the `CollectMeterEvents` GenServer

The `CollectMeterEvents` GenServer is used to record meter usage events without blocking the calling process. Uncomment the `CollectMeterEvents` GenServer to enable it:

{% code title="lib\petal\_pro\application.ex" %}

```diff
defmodule PetalPro.Application do  
  def start(_type, _args) do
    children = [
      ...
      
      # Start the meter collection GenServer
-      # PetalPro.Billing.Meters.CollectMeterEvents,
+      PetalPro.Billing.Meters.CollectMeterEvents,

      ...
    ]
    
    opts = [strategy: :one_for_one, name: PetalPro.Supervisor]
    Supervisor.start_link(children, opts)
  end  
end
```

{% endcode %}

### Enable the synchronisation job

Synchronisation with Stripe is handled by the `MeterSyncWorker` Oban job. Uncomment the Oban configuration to enable it:

{% code title="config/config.exs" %}

```diff
config :petal_pro, Oban,
  repo: PetalPro.Repo,
  queues: [default: 5, billing: 5],
  plugins: [
    {Oban.Plugins.Pruner, max_age: 3600 * 24},
    {Oban.Plugins.Cron,
     crontab: [
       # {"@daily", PetalPro.Workers.ExampleWorker}
-       # {"@daily", PetalPro.Billing.Providers.Stripe.Workers.MeterSyncWorker},
+       {"@daily", PetalPro.Billing.Providers.Stripe.Workers.MeterSyncWorker},
       # {"* * * * *", EveryMinuteWorker},
       # {"0 * * * *", EveryHourWorker},
       # {"0 */6 * * *", EverySixHoursWorker},
       # {"0 0 * * SUN", EverySundayWorker},
       # More examples: https://crontab.guru/examples.html
     ]}
  ]
```

{% endcode %}

{% hint style="info" %}
Note that there's two Oban queues. Billing/Stripe jobs utilise the `billing` queue - including the `MeterSyncWorker` job. Oban Web allows you to view/isolate separate queues!
{% endhint %}

## Handling metered usage

There are two steps with regards to handling metered usage:

1. Record the usage
2. Sync the usage with Stripe

### Record the usage

Metered usage is stored in the Petal Pro database. To record an event, use the `record_event` function:

```elixir
alias PetalPro.Billing.Meters

Meters.record_event(meter_id, customer_id, subscription_id, quantity)
```

`record_event` is non-blocking and enqueues the event using the `CollectMeterEvents` GenServer. In the background, the `CollectMeterEvents` GenServer writes the event to the database as soon as it can.&#x20;

### Sync the usage with Stripe

When it comes to metered usage events, Petal Pro is considered the source of truth. For example, when you record an event you specify the `subscription_id`. You can't filter by `subscription_id` when retrieving meter usage events via the Stripe API. However, you can when you return meter usage events from the database. This is used in the billing page to match meter usage events to the upcoming invoice.

Synchronisation with Stripe is handled by the `MeterSyncWorker` Oban job - which will upload unprocessed events to Stripe. A metered usage event is considered processed if:

* The event has been uploaded successfully (it will be marked as sent)
* There is an error (it will be marked with the error)

{% hint style="danger" %}
Meter events with errors will not synchronise with Stripe. If something is not right, check the `billing_meter_events` table for error messages. Events will be re-processed if the `error_message` is removed
{% endhint %}

Metered events are processed in batches to avoid excessive uploads using the Stripe API. How often the synchronisation is run is based on the [Oban configuration](#enable-the-collectmeterevents-genserver).&#x20;

By default, this will happen once a day. However, for testing in development you might find it useful to increase the frequency. The following will run the Oban job every minute:

{% code title="config/config.exs" %}

```diff
-{"@daily", PetalPro.Billing.Providers.Stripe.Workers.MeterSyncWorker}
+{"* * * * *", PetalPro.Billing.Providers.Stripe.Workers.MeterSyncWorker} 
```

{% endcode %}

## Updating the chat feature

Let's implement the following things to update the AI chat feature so that it can record metered usage:

* Remove month/year toggle from the subscribe page
* Change the user AI chat route - so it can load subscriptions
* Update the menu
* Update the Langchain calls to return usage
* Adjust the AI chat feature so that it can consume the usage

### Remove month/year toggle from the Subscribe page

Just remove the `interval_selector` from the `pricing_panels_container`:

{% code title="subscribe\_live.ex" %}

```diff
      <.container class="my-12">
        <.h2 class="mb-8 text-center">{gettext("Choose a plan")}</.h2>

-        <BillingComponents.pricing_panels_container panels={length(@products)} interval_selector>
+        <BillingComponents.pricing_panels_container panels={length(@products)}>
          <%= for product <- @products do %>
            <BillingComponents.pricing_panel
              id={"pricing-product-#{product.id}"}
```

{% endcode %}

### Change the user AI chat route

Recording metered usage requires a customer and subscription id:

```elixir
alias PetalPro.Billing.Meters

Meters.record_event(meter_id, customer_id, subscription_id, quantity)
```

The simplest way to get this data is through the `SubscriptionRoutes`. First, remove the existing route:

{% code title="router.ex" %}

```diff
      live "/users/org-invitations", UserOrgInvitationsLive
      live "/users/two-factor-authentication", EditTotpLive

-      live "/ai-chat", UserAiChatLive

      live "/orgs", OrgsLive, :index
      live "/orgs/new", OrgsLive, :new
```

{% endcode %}

Then add the following to `SubscriptionRoutes`:

{% code title="subscription\_routes.ex" %}

```diff
      scope "/app", PetalProWeb do
        pipe_through [:browser, :authenticated, :subscribed_user]

        get "/subscribed", PageController, :subscribed

        live_session :subscription_authenticated_user,
          on_mount: [
            {PetalProWeb.UserOnMountHooks, :require_authenticated_user},
            {PetalProWeb.OrgOnMountHooks, :assign_org_data},
            {PetalProWeb.SubscriptionPlugs, :subscribed_user}
          ] do
          live "/subscribed_live", SubscribedLive
+          live "/ai-chat", UserAiChatLive
        end
      end

      scope "/app", PetalProWeb do
        pipe_through [:browser, :authenticated, :subscribed_org]

        scope "/org/:org_slug" do
          get "/subscribed", PageController, :subscribed

          live_session :subscription_authenticated_org,
            on_mount: [
              {PetalProWeb.UserOnMountHooks, :require_authenticated_user},
              {PetalProWeb.OrgOnMountHooks, :assign_org_data},
              {PetalProWeb.SubscriptionPlugs, :subscribed_org}
            ] do
            live "/subscribed_live", SubscribedLive
+            live "/ai-chat", UserAiChatLive
          end
        end
      end

```

{% endcode %}

For a `:user` based subscription, it'll look the same:

```url
http://localhost:4000/app/ai-chat
```

But for an `:org` based subscription, the url will look something like this:

```url
http://localhost:4000/app/org/abbott-quigley/ai-chat
```

In either case, the Live View will be mounted with the current Customer and Subscription.

### Update the menu

For this guide, we're using `:org` based subscriptions. In `menus.ex` remove the `:user_chat_ai` menu item:

{% code title="menu.ex" %}

```diff
  def main_menu_items(nil), do: []

  # Signed in main menu
-  def main_menu_items(current_user), do: build_menu([:dashboard, :orgs, :subscribe, :user_ai_chat], current_user)
+  def main_menu_items(current_user), do: build_menu([:dashboard, :orgs, :subscribe], current_user)

  # Signed out user menu
  def user_menu_items(nil), do: build_menu([:sign_in, :register], nil)
```

{% endcode %}

Then update the org menu:

{% code title="org\_layout\_component.ex" %}

```diff
          [
            get_link(:org_dashboard, org),
            get_link(:org_settings, org),
-            get_link(:org_subscribe, org)
+            get_link(:org_subscribe, org),
+            get_link(:org_chat, org)
          ],
          & &1
        )
```

{% endcode %}

And add the `get_link` function:

{% code title="org\_layout\_component.ex" %}

```elixir
  defp get_link(:org_chat, org) do
    if Customers.entity() == :org do
      %{
        name: :org_chat,
        path: ~p"/app/org/#{org.slug}/ai-chat",
        label: gettext("AI Chat"),
        icon: "hero-command-line"
      }
    end
  end
```

{% endcode %}

### Update the Langchain calls to return usage

The following adds the `on_llm_token_usage` callback. To enable it, `include_usage` must be set to `true`:

{% code title="user\_ai\_chat\_live/langchain.ex" %}

```diff
            %{
              on_llm_new_delta: fn llm_chain, delta ->
                send(live_view_pid, {:chat_delta, delta, llm_chain})
+              end,
+              on_llm_token_usage: fn llm_chain, usage ->
+                send(live_view_pid, {:usage, usage, llm_chain})
              end
            }
          ],
-          stream: true
+          stream: true,
+          stream_options: %{include_usage: true}
        })
    }
    |> LLMChain.new!()
```

{% endcode %}

`on_llm_token_usage` sends a message to the calling Live View.

### Adjust the AI chat feature

Finally, add the following `handle_info` callback in `user_ai_chat_live`:

{% code title="user\_ai\_chat\_live.ex" %}

```elixir
  @impl true
  def handle_info({:usage, %Elixir.LangChain.TokenUsage{input: input, output: output}, _assistant}, socket) do
    customer = socket.assigns.customer
    subscription = socket.assigns.subscription

    meter = PetalPro.Billing.Meters.get_meter_by_event_name("ai_tokens")

    PetalPro.Billing.Meters.record_event(meter.id, customer.id, subscription.id, input + output)

    {:noreply, socket}
  end
```

{% endcode %}

This consumes the message sent from the Langchain helper. Because the code is inside the Live View and it's protected by the subscription routes, we can grab the `customer` and the `subscription` as an assign. The `input` and `output` parameters represent the number of tokens that have been used. Once we lookup the `meter` we have enough information to call `record_event`.

## Ready to go!

In the web app, login as the admin user.

{% hint style="info" %}
The default user name for the admin user is `admin@example.com`. The default password is `password`
{% endhint %}

Navigate to the Organisations page:

```
http://localhost:4000/app/orgs
```

Click on your Organisation, then click "Subscribe". You'll see the following page:

<figure><img src="https://3693973853-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYos1ARQNZ0mySw9UuAwA%2Fuploads%2F4zVMkfZHEA9v9B3fCxvu%2FScreenshot%20from%202025-07-01%2010-29-49.png?alt=media&#x26;token=26042197-0322-4a03-b836-7527996248d1" alt=""><figcaption><p>Subscribe page for an Organisation</p></figcaption></figure>

Choose a plan to subscribe to. Then use Stripe to complete the process - you can use `4242 4242 4242 4242` as your credit card for testing.

{% hint style="info" %}
You must have the Stripe [web hook](#run-the-cli-web-hook) running so that you can complete this process
{% endhint %}

Once you have completed the process, go back to the Organisations page and click on "AI Chat":

<figure><img src="https://3693973853-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYos1ARQNZ0mySw9UuAwA%2Fuploads%2F1lRWMrci7KiVV1QvYGcA%2FScreenshot%20from%202025-07-01%2011-37-02.png?alt=media&#x26;token=3e86a2af-61af-4e19-bee9-882f121bfea0" alt=""><figcaption><p>Org AI Chat!</p></figcaption></figure>

Ask a question - this will trigger the code that collects metered usage. Now go to the Admin page and click on "Oban Web":

<figure><img src="https://3693973853-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYos1ARQNZ0mySw9UuAwA%2Fuploads%2FbacV6SinrwUvMdLXyfsp%2FScreenshot%20from%202025-07-01%2011-38-52.png?alt=media&#x26;token=4ffa5c86-2e28-479f-aaf0-b17b74ca71a7" alt=""><figcaption><p>Oban Web</p></figcaption></figure>

Make sure that the `MeterSyncWorker` job has successfully completed.&#x20;

Finally, go to your Organisation, then click on "Org Settings" and "Billing". You should see something similar to this:

<figure><img src="https://3693973853-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FYos1ARQNZ0mySw9UuAwA%2Fuploads%2FRKFgeoD1l1Qx83qxahxO%2FScreenshot%20from%202025-07-01%2011-49-55.png?alt=media&#x26;token=799e7e70-dd5b-49aa-8f0d-e145f509d714" alt=""><figcaption><p>Organisation Billing page</p></figcaption></figure>

If everything went well, you should see an "Upcoming" amount and "AI Tokens" usage data . If you configure multiple Meters, they will be automatically grouped on this screen.

{% hint style="info" %}
It takes time for the upcoming invoice to be updated in Stripe. If you're quick, give it at least 30 seconds!
{% endhint %}

## Deployment with Fly.io

Once your demo is ready, you can push it to production. To do so, there's three steps:

* Push the demo app to Fly.io
* Setup webhook endpoint on Stripe
* Configure Fly.io environment variables

### Push the demo app to Fly.io

To start with Fly.io, use the following guide:

{% content-ref url="deploy-to-fly.io" %}
[deploy-to-fly.io](https://docs.petal.build/petal-pro-documentation/guides/deploy-to-fly.io)
{% endcontent-ref %}

### Setup webhook endpoint on Stripe

You can use the Stripe CLI to run the webhook when working on your own dev machine. However, for a hosted service, you need to configure an endpoint in the Stripe console:

1. Open the Stripe console
2. Make sure you're in "Test mode"
3. Click on `Developers` at the top of the page
4. Then click on `Webhooks`
5. Click on `Add endpoint`

For the `Endpoint url` enter the following (replace `your-host-name` with the name of your fly application):

`https://your-host-name.fly.dev/webhooks/stripe`

Further down the page under the heading "Select events to listen to" - click on `+ Select events`. Add the following events to the list:

* `customer.subscription.created`; and
* `customer.subscription.updated`

Finally, click the `Add endpoint` button.

### Configure Fly.io environment variables

The demo app running in Fly.io needs to be configured for Stripe. We need two settings. First, to obtain the `STRIPE_SECRET`:

1. In Stripe, make sure you're in "Test mode"
2. Click on `Developers`
3. Click on `API Keys`
4. Click on `Reveal test key`
5. Click the key to copy it. This is for our `STRIPE_SECRET` environment variable

Next, to obtain the `STRIPE_WEBHOOK_SECRET`:

1. In `Developers` click on `Webhooks`
2. Click on the endpoint you just created
3. Under "Signing secret" click on `Reveal`
4. Copy the secret

Now at the command line, run the following command:

{% code title="Terminal" %}

```bash
fly secrets set _
  STRIPE_SECRET="sk_test_xxx" _
  STRIPE_WEBHOOK_SECRET="whsec_xxx" _
  STRIPE_PRODUCTION_MODE="false"
```

{% endcode %}

Once Fly has finished deploying you're new secrets, you should be able to purchase a subscription using the Fly demo app!

### Using the CLI with "Live mode"

The Stripe CLI defaults to "Test mode". To work in "Live mode" you'll need to pass in an extra parameter. For example, if you wanted to list products in live mode you would do the following:

```bash
stripe products list --live
```

Not all Stripe CLI commands support the `--live` flag. But it will work with the commands listed in this tutorial.

### Recreate the Stripe Products

You'll need to repeat the [Configure stripe](#configure-stripe) section - re-creating the products in "Live mode". This means that you'll end up creating the same products, but they will have different ids. Before you update the configuration in `/config/config.exs`, I suggest that you copy the following section to `/config/dev.exs`:

```elixir
config :petal_pro, :billing_products, [
  %{
    id: "essential",
    name: "Essential",
    description: "Essential description",
    most_popular: true,
    # ...
  }
```

That way you can stick with "Test mode" on your dev machine and then use `/config/config.exs` for your production server.
