Stripe Restricted Keys · Lovable
Stripe restricted API key for a Lovable app: exact scopes + where Lovable alone isn't enough
The five-ish permissions you actually need, one Lovable-specific gotcha that trips up every first build, and the three controls nobody ships you by default — not Stripe, not Lovable, nobody.
TL;DR
For a standard Lovable-generated checkout flow (create PaymentIntent → confirm → webhook → mark order paid): tick Charges: Write, PaymentIntents: Write, Customers: Write, Webhook Endpoints: Read, and Products/Prices: Read. Do not tick Refunds: Write from the Lovable-facing key — put refunds behind your admin code path with a separate key. Lovable's generated server code will happily use whatever key you give it, which is precisely the risk: the key is long-lived, has no spend cap, and nothing between Lovable and Stripe logs what the agent writing your code actually tried to call.
Why this page exists
Lovable generates a full-stack app — React front end, a small server, and Stripe wiring — from a prompt. You paste a Stripe Secret Key into the project's secrets panel, and the generated code calls the Stripe SDK with it. That works. It is also, by default, a secret key with everything enabled, held by AI-generated code you did not write, in a project where the LLM can be re-prompted to add, remove, or rewrite endpoints between deploys. A Restricted Key narrows that blast radius to just the scopes the generated code actually needs.
The rest of this page is the exact scope list, a table of which Lovable-generated patterns need which scope, the Lovable-specific webhook gotcha, and the three guardrails you still don't have even with a tightly scoped Restricted Key.
The exact scope set
Go to Stripe Dashboard → Developers → API keys → Restricted keys → Create restricted key. Name the key lovable-<project-slug>-live so the access log has enough to triage. Tick:
# minimum scope for a typical Lovable checkout build
Charges → Write
PaymentIntents → Write
Customers → Write
Products → Read
Prices → Read
Webhook Endpoints → Read (only needed if the generated code calls stripe.webhookEndpoints.list)
Checkout Sessions → Write (only if the app uses Stripe Checkout rather than custom Elements)
# everything else → None
That's the live-mode key. Create an identical-scope rk_test_… key for the Lovable preview environment — Lovable lets you store separate test/live secrets per deploy target and you should use that, not a single key toggled between modes.
Which Lovable patterns need which scope
| Lovable feature (as it shows up in generated code) | Stripe API call | Scope required |
|---|---|---|
| "Create Stripe Customer on sign up" | customers.create | Customers: Write |
| "Use Stripe Checkout for one-time payment" | checkout.sessions.create | Checkout Sessions: Write (+ Prices: Read) |
| "Custom Elements payment form" | paymentIntents.create, paymentIntents.confirm | PaymentIntents: Write |
| "Charge a saved card" | paymentIntents.create with customer + payment_method | PaymentIntents: Write (+ Customers: Read implied) |
| "Webhook handler for payment success" | signature check only — no API call | None (uses the webhook signing secret, not the API key) |
| "List products in a Lovable admin page" | products.list, prices.list | Products: Read, Prices: Read |
| "Refund a payment from the admin panel" | refunds.create | Use a separate key. Don't put refunds behind the same RK the checkout path uses. |
| "Subscription signup / upgrade / cancel" | subscriptions.create, subscriptions.update | Subscriptions: Write, Invoices: Read |
If your Lovable project does none of subscriptions, checkout-sessions, or refunds, you can cut the scope list down further. The rule of thumb: read the last 50 lines of generated server code, grep for stripe., and make sure every chained method maps to exactly one row in the Create restricted key form.
The Lovable-specific webhook gotcha
Lovable generated code often ships a line like:
const endpoints = await stripe.webhookEndpoints.list();
if (!endpoints.data.find(e => e.url === MY_URL)) {
await stripe.webhookEndpoints.create({ url: MY_URL, enabled_events: [...] });
}
That "auto-register the webhook on first boot" pattern wants Webhook Endpoints: Write. Give it Read only and the first deploy will log an error, but the app still works if you register the webhook once from the Stripe Dashboard. Handing a Restricted Key the permission to rewrite your own webhook routing table is almost always overkill — Lovable will regenerate that boot code without telling you, and an unguarded webhookEndpoints.update can silently redirect payment events to a URL you no longer own.
Recommended: register the webhook by hand in the Dashboard, delete the auto-register block from the generated code, and keep Webhook Endpoints: Read if you want, None if you don't.
Where even a perfect scope list stops being enough
Scope limits which endpoints. It does not limit the three things that actually bite Lovable builds in production:
- There is no $ cap. A stuck retry loop on
paymentIntents.create— or a re-prompted Lovable agent that adds a "retry 5× on any failure" wrapper around the checkout call — will create PaymentIntents until Stripe's own rate limits kick in. Restricted Keys have no daily dollar budget. Your first warning is a dashboard you don't look at often. - The key can't be scoped to a customer cohort.
Customers: Writeis global across your Stripe account. Any customer exists to this key. If you build a multi-tenant Lovable app and the tenant logic has a bug, one tenant's code path can touch another tenant's Stripe customer. - Mid-run revoke is a human workflow. If you catch the Lovable app doing something wrong at 11pm, your options are: rotate the key in the Stripe Dashboard (30 seconds, plus however long it takes to redeploy Lovable with the new secret — often 2-5 minutes) or manually cancel each PaymentIntent (slow, error-prone). There is no sub-second kill switch.
These aren't Lovable problems — Stripe itself doesn't give you spend caps, customer-scoping, or fast revoke on Restricted Keys. Lovable just amplifies the gap because the generated code changes between deploys and is written by an LLM that hasn't read your risk model.
What actually fixes this
Put something between the Lovable app and api.stripe.com that speaks Stripe's API, holds your real Restricted Key as its upstream, issues scoped per-deploy vault keys to the Lovable project, and enforces the three missing controls: a per-day USD cap (if the Lovable app burns past $200 in charges in a day, every subsequent call returns 403 policy_cap_exceeded), a customer-ID allowlist (fed from your own tenant table), and a revoke endpoint you can hit in one HTTP call that takes effect on the next Lovable request.
That is exactly what Keybrake does. You register your Stripe Restricted Key once in Keybrake, issue a vault_live_… key to paste into Lovable's secrets panel instead of the real one, attach a policy, and every api.stripe.com call the generated code makes becomes a proxy.keybrake.com/stripe/v1/… call we route, enforce, log, and can kill. Your Restricted Key never leaves our vault.
Related questions
Can I use a Lovable preview environment with the same Restricted Key as live?
Don't. Lovable previews rebuild on every edit — a regenerated code path can call a scope you'd want tightly reviewed in production. Use a test-mode key (rk_test_…) with identical scopes in preview, and swap to the live key only in the deployed target. Stripe's test mode is free, so there is no cost to this split.
Does Lovable's "Stripe Connect" integration need a different Restricted Key?
Yes — Connect needs either a secret key on the platform account (too broad) or a Restricted Key with Stripe-Account header support. In the Create Restricted Key form, tick the Connect scopes that match what your generated code does (typically Connect → All Connect resources: Read plus whichever write verbs your platform uses) and keep the non-Connect scopes from the table above. The Restricted Key still has no per-account spend cap — if you run payouts or transfers per connected account, you'll want a proxy that enforces it.
What happens if Lovable regenerates server code and starts calling a Stripe endpoint the Restricted Key blocks?
Stripe returns 401 Unauthorized with a "restricted key does not have the required permissions" message. The Lovable runtime surfaces it as a 500 on the user-facing request. That is a feature — the failure is loud — but the fix is to read the diff Lovable generated and decide whether to widen the scope or roll back the prompt. Don't fall into the habit of reflexively ticking more scopes to make the error go away.
Is there a Restricted Key setting that caps daily charge volume?
No. As of early 2026, Stripe's Restricted Key system is permission-based only. There is an open request (stripe/agent-toolkit issue #356) for a governance layer that would add per-key spend caps — it has not shipped. Until it does, that control lives outside Stripe, in a proxy or in your own server code.
Does Lovable show me which Restricted Key it uses for a given deploy?
Only indirectly. The secret name is visible in Lovable's environment-variables panel; the actual value is write-only after it's set. If you rotate the key in Stripe, you must update the Lovable secret and redeploy for the new key to take effect. We strongly recommend one key per Lovable project — not one key shared across projects — so you can tell which deploy caused a dashboard alert.
Further reading
- Stripe restricted API key example — the five-tick scope set for a refund-only agent, with the full walkthrough.
- Stripe Restricted Key permissions map — every toggle, what it unlocks, and the runaway-risk rating per resource.
- Stripe API key with restricted access — answers "does the built-in Restricted Key feature cover the 10 controls I care about?" honestly.
- How to give an AI agent a Stripe API key without losing $4,000 to a stuck loop — the five-control checklist applied end-to-end.