Quickstart Billing Subscriptions Deployment Kamal

Paddle subscriptions

Here’s how to understand and configure Paddle Billing subscriptions in Business Class.

Paddle Billing

To set up Paddle Billing, read the Paddle developer docs and prepare the API and secret keys.

To update your credentials:

$ VISUAL="code --wait" bin/rails credentials:edit --environment=development
$ VISUAL="code --wait" bin/rails credentials:edit --environment=production

You’ll need to provide the following Paddle configuration as part of your credentials file:

  environment: sandbox
  api_key: yyyy
  client_token: ppp
  signing_secret: zzz

You can find vendor ID and API key in the Authentication section under Developer Tools in the Paddle dashboard. You’ll get the webhook secret when setting up the notifications.

Alternatively to Rails credentials, you can also set credentials using the environment variables:


For development, you need to create a sandbox environment separated from a production account. If you don’t set Paddle environment you can still test the application using the fake subscriptions.

The test environment doesn’t need any credentials set, only development (with a sandbox) and production need that.


You can create your plans under Catalog -> Products in the Paddle dashboard. One product can have several different prices. Note your product ID.

Then add them in the paddle.rb initializer:

# Fill this in with your own plans. Only the price ID (`id`) is what we work
# with in terms of subscription management, the rest is for local UI/UX only.
  # {
  #   id: "pri_01h9a8t6kthzq2x4yaspbf06mh",
  #   name: "Plan name",
  #   trial_period: "1",
  #   price: "$19/mo"
  # },
  # {
  #   id: "pri_01h86bv3sg853ktbg29z11amcc",
  #   name: "Plan name",
  #   trial_period: "0",
  #   price: "$10/mo"
  # }

You can also fetch this data with:

> Paddle::Price.list(product_id: Pay::PaddleBilling.product_id)&.data

Apart from the price ID, fill in whatever you want to show up in the UI. Prices are not fetched automatically to avoid dealing with slow requests or caches.

After you create your plans in Paddle, it will be possible to subscribe to them as part of the create new team/space/organization flow.

  • Plans with trials start with the status trialing and the attribute trial_ends_at set.

  • After trial is over, customer is charged and the status moves to active. A subscription without a trial is simply active from day 1.

  • Active subscriptions can be paused and resumed both from the application or Paddle. Subscription is still considered active even if scheduled for change. At this time the subscription is on a grace period and considered pause or cancelled only after the grace period is over.

  • Paused subscriptions are determined by pause_starts_at and cancelled subscriptions by ends_at.

  • Subscriptions on a grace period cannot be cancelled straight away, you need to cancel the scheduled change first.

  • Cancelled subscriptions cannot be resumed.


To configure webhooks head over to Developer Tools -> Notifications in the Paddle dashboard.

Set up a new destination with the webhook path /pay/webhooks/paddle (replace your domain name):


Choose subscription.created and subscription.updated subscriptions. Note the secret key.

If you want to test webhooks locally, you can use a tool like Ngrok which will give you a public address:

$ ngrok http 3000
Forwarding  https://406b-80-250-28-104.eu.ngrok.io -> http://localhost:3000

Then use this address and add it to the Paddle Sandbox under Developer Tools -> Notifications:


Make sure it’s a primary address.

Next, remove the following condition in teams#success:

# Webhook won't arrive so let's pretend things are well
create_fake_subscription if Rails.env.development?

And allow the host in config/environments/development.rb by adding it to config.hosts.

For a production environment you also have to approve your domain names.


Pay webhooks should ensure that subscriptions are always synced. Nevertheless, you can always run the following Rake task to synchronize subscriptions’s state:

$ bin/rails paddle:sync