Skip to main content
Lavendly ships a one-shot setup script that creates every Stripe product, price, and webhook endpoint the API needs. Run it once per environment (sandbox + production) and the billing flow is live.

What it creates

Lookup keyKindDisplayPrice (USD)Grants
lavendly_starter_monthlyrecurringLavendly Starter$29 / moStarter plan
lavendly_creator_monthlyrecurringLavendly Creator$89 / moCreator plan
lavendly_studio_monthlyrecurringLavendly Studio$249 / moStudio plan
lavendly_pack_100one-time100 credits$12+100 credits
lavendly_pack_500one-time500 credits$55+500 credits
lavendly_pack_1500one-time1500 credits$149+1500 credits
lavendly_pack_5000one-time5000 credits$400+5000 credits
Every price uses a stable lookup_key, so re-running the script never creates duplicates, it finds existing prices and skips them.

One-shot flow

1

Get a sandbox key

Stripe dashboard → test mode → API keys.Use the Secret key (starts with sk_test_…) or a Restricted key (rk_test_…) with these permissions enabled:
  • Products → Write
  • Prices → Write
  • Webhook endpoints → Write (only if you pass --webhook)
2

Drop it into .env.local

STRIPE_SECRET_KEY=sk_test_…
STRIPE_API_KEY=rk_test_… is also accepted if that’s the var name your tooling uses.
3

Dry run

set -a; source .env.local; set +a
npm run stripe:setup
Prints exactly what it would create. No API mutation.
4

Apply and write back the price IDs

npm run stripe:setup -- --apply --write
Creates every product + price (idempotent via lookup_key), then appends or updates the STRIPE_PRICE_* block in .env.local.
5

Register the webhook (optional, recommended)

npm run stripe:setup -- --apply --write \
  --webhook https://api.yourdomain.com/v1/stripe/webhook
Creates the endpoint subscribed to:
  • checkout.session.completed
  • customer.subscription.deleted
  • customer.subscription.paused
  • invoice.payment_failed
The signing secret is printed on first creation, copy it into STRIPE_WEBHOOK_SECRET. On re-runs it’s stored in the dashboard (Webhooks → endpoint → Signing secret → click to reveal).
6

Restart the API to pick up the new env

docker compose up -d --build lavendly-api
7

Verify

Open the canvas, click any “Generate” CTA on a workflow that requires more credits than you have, and the inline buy-credits sheet should pop with three real Stripe Checkout tiles.

Going to production

The exact same script with a live key:
# Verify .env.local has STRIPE_SECRET_KEY=sk_live_…
node scripts/stripe-setup.mjs --live              # dry run
node scripts/stripe-setup.mjs --live --apply --write \
  --webhook https://api.yourdomain.com/v1/stripe/webhook
The --live flag is REQUIRED for live keys. Without it the script refuses to run, even in dry mode. There’s no auto-detection, you explicitly opt in.

Re-running safely

The script is idempotent. Re-running it:
  • Finds each existing product by name → reuses it
  • Finds each existing price by lookup_key → reuses it
  • Finds the existing webhook by URL → updates the subscribed events
If you want to change a price (say bumping Starter from 29to29 to 39), the script will not touch the existing price (Stripe prices are immutable once created). Instead:
  1. Archive the old price in the Stripe dashboard.
  2. Pick a new lookup_key (e.g. lavendly_starter_monthly_v2) in scripts/stripe-setup.mjs.
  3. Re-run with --apply.
  4. Update the corresponding STRIPE_PRICE_* env var.

Troubleshooting

Your key isn’t a Stripe secret/restricted key. Check the prefix, valid forms are sk_test_, sk_live_, rk_test_, rk_live_.
Restricted key with insufficient permissions. Edit the key in the Stripe dashboard and grant Write on Products + Prices (and Webhook endpoints if you use --webhook).
Pass --live explicitly. The script never assumes you want to touch your live account.