To secure a Lovable + Stripe stack, 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 blueprint covers key management, webhook security, and payment flow best practices.
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 signature.
Alternative Stack Options
Consider these related blueprints for different stack combinations:
- Lovable + Supabase - Store Stripe metadata in Supabase
- Lovable + Firebase - Firebase for payment webhooks
- Lovable + Vercel - Deploy Stripe webhooks on Vercel