Docs · Billing & Entitlements

Stripe Setup

Getting your secret key, webhook secret, and price IDs, and setting a statement descriptor.

Turning on real billing takes three environment variables and one setting inside Stripe itself. Nothing here is needed to develop against the app: the Local fake covers that. This is the checklist for the point where you're ready to charge a real card.

The three variables

  • STRIPE_SECRET_KEY: start with a test key (sk_test_...) while you're integrating, and swap to a live key only when you're ready to charge real cards.
  • STRIPE_WEBHOOK_SECRET: from your webhook endpoint at /webhooks/stripe. Locally, run stripe listen --forward-to localhost:3000/webhooks/stripe and use the secret it prints.
  • A price id per plan, for example STRIPE_PRICE_PRO: the id of a recurring Price you create in Stripe, either directly in the dashboard or through the Stripe MCP server (see .mcp.json).

Secrets resolve ENV first, then Rails encrypted credentials (bin/rails credentials:edit) as a fallback. Locally, put them in .env (copy .env.example as a starting point); in production, inject them via Kamal secrets. See First Deploy Checklist.

Set your statement descriptor before you charge anyone

By default, a customer's card statement shows your Stripe account's registered business name, not your product's name. Someone who bought from yourapp.com sees an unfamiliar legal entity on their statement, doesn't recognize it, and disputes the charge. Unrecognized descriptors are a leading cause of chargebacks, and the risk compounds if one Stripe account serves more than one product.

For subscriptions, the descriptor comes from the Stripe Product (max 22 characters, shown in capitals), with precedence running invoice, then product, then account default. Set it on each product:

Stripe::Product.update("prod_...", statement_descriptor: "YOURAPP.COM")

Your domain is usually the clearest choice: it's what the customer remembers paying. A product created inline from price_data cannot carry a descriptor at all, which is the reason to create real Products and Prices in Stripe and reference their ids from environment variables, rather than building line items inline at checkout time.

Webhook events to enable

Your Stripe webhook endpoint needs to receive subscription lifecycle events: created, updated, canceled, and payment failures, at minimum. Missing one of these means the app's local billing state can drift from what Stripe actually has on file, since entitled? reads that local state rather than calling Stripe on every request. See First Deploy Checklist for the full list this kit's own deploy expects.

Verify

With test-mode keys in place, walk through a full subscribe-and-cancel cycle on the pricing page and confirm the webhook fires and updates entitlement correctly, before pointing the same setup at live keys.

Next

If you want to run a limited-time discount instead of (or alongside) your standard pricing, see Launch Pricing.