To connect Stripe to a Lovable app securely, you need to: (1) keep Secret Keys server-side only, (2) verify webhook signatures on every event, (3) use Stripe Checkout or Elements for PCI compliance, and (4) create payment intents on the server. This integration guide covers key management, webhook security, and payment flow patterns.
TL;DR
Stripe integration requires careful key management and webhook verification. Key rules: never expose the Secret Key (sk_*) in client code, always verify webhook signatures, use Stripe Checkout or Elements for PCI compliance, and create payment intents on the server only. Test with Stripe's test mode before going live.
Stripe Key Management
| Key Type | Prefix | Where to Use |
|---|---|---|
| Publishable Key | pk_test_ / pk_live_ | Client-side (safe to expose) |
| Secret Key | sk_test_ / sk_live_ | Server-side only (NEVER expose) |
| Webhook Secret | whsec_ | Server-side only |
Part 1: Stripe Environment Variables
# Client-side (safe)
VITE_STRIPE_PUBLISHABLE_KEY=pk_test_xxx
# Server-side only (NEVER in client code)
STRIPE_SECRET_KEY=sk_test_xxx
STRIPE_WEBHOOK_SECRET=whsec_xxx
Critical: If the Secret Key (sk_*) is ever exposed, rotate it immediately in the Stripe dashboard. An exposed key allows full access to your Stripe account.
Part 2: Stripe Webhook Verification
import Stripe from 'stripe';
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function handleWebhook(request: Request) {
const body = await request.text();
const signature = request.headers.get('stripe-signature');
let event: Stripe.Event;
try {
event = stripe.webhooks.constructEvent(
body,
signature!,
process.env.STRIPE_WEBHOOK_SECRET!
);
} catch (err) {
console.error('Webhook signature verification failed');
return new Response('Invalid signature', { status: 400 });
}
// Handle the verified event
switch (event.type) {
case 'checkout.session.completed':
// Fulfill the purchase
break;
case 'payment_intent.succeeded':
// Handle successful payment
break;
}
return new Response('OK', { status: 200 });
}
Part 3: Stripe Payment Flow
// This must run on the server, not the client
export async function createCheckoutSession(userId: string, priceId: string) {
const session = await stripe.checkout.sessions.create({
customer_email: userEmail, // Or use customer ID
line_items: [{ price: priceId, quantity: 1 }],
mode: 'subscription',
success_url: `${process.env.APP_URL}/success?session_id={CHECKOUT_SESSION_ID}`,
cancel_url: `${process.env.APP_URL}/cancel`,
metadata: { userId }, // For webhook processing
});
return session.url;
}
Security Checklist
Pre-Launch Checklist for Lovable + Stripe
Secret Key only in server-side code
Webhook signature verification implemented
Using Stripe Checkout or Elements (PCI compliance)
Payment intents created server-side only
Test mode used for development
Live keys in production environment only
Webhook endpoint secured (HTTPS)
Idempotency keys used for retries
Is the publishable key safe to expose?
Yes, the publishable key (pk_*) is designed for client-side use. It can only be used to create tokens or confirm payments, not to read data or make charges.
Why verify webhook signatures?
Without verification, attackers could send fake webhook events to your endpoint, potentially granting free access or disrupting your service. Always verify the stripe-signature header before processing any event.
Does Stripe Checkout make my Lovable app PCI compliant?
Using Stripe Checkout or Stripe Elements means card data never touches your server, which covers most PCI DSS scope for small SaaS apps. You still need HTTPS and must not log card data anywhere in your Lovable app.
Integrating Stripe with Lovable?
Scan for key exposure and webhook issues.