Add free trials to Stripe subscriptions using trial_period_days on Checkout Sessions or Subscription creation. With payment method upfront, set trial_period_days in subscription_data. Without payment method, use payment_method_collection: 'if_required' and configure trial_settings.end_behavior to cancel or pause when the trial ends. Always listen for the customer.subscription.trial_will_end webhook to prompt users before their trial expires.
How Free Trials Work in Stripe Subscriptions
Stripe supports two trial models: trials with a payment method collected upfront (higher conversion) and trials without a payment method (lower friction). Both use trial_period_days to set the trial length. During the trial, the subscription status is 'trialing' and no charges are made. When the trial ends, Stripe automatically charges the customer's payment method or takes the configured end_behavior action. The customer.subscription.trial_will_end webhook fires approximately 3 days before trial expiration, giving you time to notify users.
Prerequisites
- A Stripe account with API keys (test mode is fine)
- Products and Prices created in the Stripe Dashboard
- A server running Node.js with the stripe package installed
- A webhook endpoint configured to receive Stripe events
Step-by-step guide
Create a Checkout Session with a trial period
Create a Checkout Session with a trial period
The most common trial setup collects a payment method upfront during checkout but doesn't charge until the trial ends. Add trial_period_days to the subscription_data object in your Checkout Session. The customer enters their card details during checkout, but the first invoice is for $0. After the trial period, Stripe automatically charges the subscription price.
1const session = await stripe.checkout.sessions.create({2 mode: 'subscription',3 line_items: [{ price: 'price_xxx', quantity: 1 }],4 subscription_data: { trial_period_days: 14 },5 success_url: 'https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}',6 cancel_url: 'https://yoursite.com/cancel',7});Expected result: Customer completes checkout, enters payment details, and starts a 14-day free trial with no charge.
Create a trial without requiring a payment method
Create a trial without requiring a payment method
For maximum signups, offer trials without collecting a card. Set payment_method_collection to 'if_required' and configure what happens when the trial ends without a payment method. Options are 'cancel' (terminates the subscription), 'pause' (pauses until payment method added), or 'create_invoice' (creates an unpaid invoice).
1const session = await stripe.checkout.sessions.create({2 mode: 'subscription',3 line_items: [{ price: 'price_xxx', quantity: 1 }],4 subscription_data: {5 trial_period_days: 14,6 trial_settings: {7 end_behavior: { missing_payment_method: 'cancel' },8 },9 },10 payment_method_collection: 'if_required',11 success_url: 'https://yoursite.com/success',12 cancel_url: 'https://yoursite.com/cancel',13});Expected result: Customer starts a trial without entering any payment information.
Create a trial via the Subscriptions API directly
Create a trial via the Subscriptions API directly
If you manage your own checkout UI, create subscriptions with trials programmatically. Create or retrieve a customer, then create a subscription with trial_period_days. The subscription starts in 'trialing' status immediately. For trials with payment method, attach a PaymentMethod to the customer first.
1const subscription = await stripe.subscriptions.create({2 customer: 'cus_xxx',3 items: [{ price: 'price_xxx' }],4 trial_period_days: 14,5 payment_behavior: 'default_incomplete',6 expand: ['latest_invoice.payment_intent'],7});8console.log(subscription.status); // 'trialing'9console.log(subscription.trial_end); // Unix timestampExpected result: Subscription created with status 'trialing' and trial_end set to 14 days from now.
Listen for the trial_will_end webhook
Listen for the trial_will_end webhook
Stripe fires customer.subscription.trial_will_end approximately 3 days before the trial expires. Use this webhook to send reminder emails prompting users to add a payment method or upgrade. Also listen for customer.subscription.updated to detect when trials transition to active or canceled status.
1app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {2 const sig = req.headers['stripe-signature'];3 const event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);45 switch (event.type) {6 case 'customer.subscription.trial_will_end': {7 const sub = event.data.object;8 console.log(`Trial ending for ${sub.customer}`);9 // sendTrialEndingEmail(sub.customer);10 break;11 }12 case 'customer.subscription.updated': {13 const sub = event.data.object;14 if (sub.status === 'active') {15 console.log('Trial converted to paid!');16 }17 break;18 }19 }20 res.json({ received: true });21});Expected result: Your server receives a webhook ~3 days before trial ends and can send reminder emails.
Grant access based on subscription status
Grant access based on subscription status
In your application logic, check the subscription status to determine access. Both 'trialing' and 'active' statuses should grant full access. When the status changes to 'canceled', 'past_due', or 'unpaid', restrict access. Store the subscription status in your database and update it via webhooks.
1function hasAccess(subscriptionStatus) {2 return ['trialing', 'active'].includes(subscriptionStatus);3}Expected result: Users in 'trialing' or 'active' status can access premium features; others are restricted.
Complete working example
1const express = require('express');2const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);3const app = express();45// Webhook endpoint — must be before express.json()6app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {7 const sig = req.headers['stripe-signature'];8 let event;9 try {10 event = stripe.webhooks.constructEvent(11 req.body, sig, process.env.STRIPE_WEBHOOK_SECRET12 );13 } catch (err) {14 return res.status(400).send(`Webhook Error: ${err.message}`);15 }1617 switch (event.type) {18 case 'customer.subscription.trial_will_end':19 console.log('Trial ending soon:', event.data.object.customer);20 break;21 case 'customer.subscription.updated': {22 const sub = event.data.object;23 console.log(`Subscription ${sub.id} status: ${sub.status}`);24 break;25 }26 case 'invoice.paid':27 console.log('Payment succeeded:', event.data.object.subscription);28 break;29 case 'invoice.payment_failed':30 console.log('Payment failed:', event.data.object.subscription);31 break;32 }33 res.json({ received: true });34});3536app.use(express.json());3738// Create trial checkout session39app.post('/create-trial', async (req, res) => {40 try {41 const session = await stripe.checkout.sessions.create({42 mode: 'subscription',43 line_items: [{ price: req.body.priceId, quantity: 1 }],44 subscription_data: { trial_period_days: 14 },45 success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`,46 cancel_url: `${req.headers.origin}/cancel`,47 });48 res.json({ url: session.url });49 } catch (err) {50 res.status(500).json({ error: err.message });51 }52});5354app.listen(4242, () => console.log('Server on port 4242'));Common mistakes when implementing subscription trials with Stripe
Why it's a problem: Not handling the trial_will_end webhook
How to avoid: Always listen for customer.subscription.trial_will_end to send reminder emails ~3 days before trial expiry.
Why it's a problem: Treating 'trialing' as inactive in your access logic
How to avoid: Both 'trialing' and 'active' subscription statuses should grant full access to premium features.
Why it's a problem: Not protecting against trial abuse with multiple signups
How to avoid: Use Stripe Radar's trial fraud blocking (stops 62% of abuse), implement email verification, and check for duplicate emails.
Why it's a problem: Using a 30-day trial when 14 days would convert better
How to avoid: Data shows 7-14 day trials convert best. Longer trials lead to users forgetting they signed up.
Best practices
- Use 7 or 14-day trials for optimal conversion rates
- Always collect a payment method upfront unless you have a specific strategy for cardless trials
- Send a reminder email 3 days before trial ends using the trial_will_end webhook
- Grant full access during trials — restricted trials reduce perceived value
- Track trial-to-paid conversion rates in your analytics
- Enable Stripe's trial messaging in Dashboard for card network compliance
- Use test mode to verify the complete trial lifecycle before going live
Still stuck?
Copy one of these prompts to get a personalized, step-by-step explanation.
I need to add a 14-day free trial to my Stripe subscription using Node.js and Express. The trial should collect a credit card upfront but not charge until day 14. Show me the Checkout Session creation and webhook handler for trial_will_end events.
Add a 14-day free trial to my Stripe subscription flow. Use Checkout Sessions with trial_period_days: 14 in subscription_data. Collect payment method upfront. Add webhook handler for customer.subscription.trial_will_end to send reminder emails. Include access control that grants full access for both 'trialing' and 'active' statuses.
Frequently asked questions
Can I change the trial length after a subscription is created?
Yes, update the trial_end on an existing subscription via API. Set it to a new Unix timestamp or 'now' to end the trial immediately, triggering a prorated charge.
What happens when a trial ends and the payment fails?
The subscription moves to 'past_due' status and Stripe's Smart Retries begin automatically, recovering 56-57% of failed payments on average.
Can I offer a trial without Stripe Checkout?
Yes. Create a subscription directly via the API with trial_period_days. Handle your own payment form using Stripe Elements and attach a PaymentMethod first.
How do I prevent trial abuse?
Enable Stripe Radar's trial fraud blocking (stops 62% of abuse), implement email verification, track device fingerprints, and check for duplicate emails.
Does the customer see a $0 charge during the trial?
Yes, Stripe creates a $0 invoice when the trial starts if a payment method is collected, confirming the card is valid without charging it.
What if I need help with complex trial and subscription logic?
For trials with usage-based billing, tiered access, or complex conversion funnels, RapidDev's engineers can implement the complete Stripe integration including webhooks and dunning flows.
Talk to an Expert
Our team has built 600+ apps. Get personalized help with your project.
Book a free consultation