# How to implement subscription trials with Stripe

- Tool: Stripe
- Difficulty: Intermediate
- Time required: 15-20 minutes
- Compatibility: Stripe API v2024-12-18+, any backend language
- Last updated: March 2026

## TL;DR

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.

## Before you start

- 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

### 1. 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.

```
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  subscription_data: { trial_period_days: 14 },
  success_url: 'https://yoursite.com/success?session_id={CHECKOUT_SESSION_ID}',
  cancel_url: 'https://yoursite.com/cancel',
});
```

**Expected result:** Customer completes checkout, enters payment details, and starts a 14-day free trial with no charge.

### 2. 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).

```
const session = await stripe.checkout.sessions.create({
  mode: 'subscription',
  line_items: [{ price: 'price_xxx', quantity: 1 }],
  subscription_data: {
    trial_period_days: 14,
    trial_settings: {
      end_behavior: { missing_payment_method: 'cancel' },
    },
  },
  payment_method_collection: 'if_required',
  success_url: 'https://yoursite.com/success',
  cancel_url: 'https://yoursite.com/cancel',
});
```

**Expected result:** Customer starts a trial without entering any payment information.

### 3. 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.

```
const subscription = await stripe.subscriptions.create({
  customer: 'cus_xxx',
  items: [{ price: 'price_xxx' }],
  trial_period_days: 14,
  payment_behavior: 'default_incomplete',
  expand: ['latest_invoice.payment_intent'],
});
console.log(subscription.status); // 'trialing'
console.log(subscription.trial_end); // Unix timestamp
```

**Expected result:** Subscription created with status 'trialing' and trial_end set to 14 days from now.

### 4. 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.

```
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  const event = stripe.webhooks.constructEvent(req.body, sig, endpointSecret);

  switch (event.type) {
    case 'customer.subscription.trial_will_end': {
      const sub = event.data.object;
      console.log(`Trial ending for ${sub.customer}`);
      // sendTrialEndingEmail(sub.customer);
      break;
    }
    case 'customer.subscription.updated': {
      const sub = event.data.object;
      if (sub.status === 'active') {
        console.log('Trial converted to paid!');
      }
      break;
    }
  }
  res.json({ received: true });
});
```

**Expected result:** Your server receives a webhook ~3 days before trial ends and can send reminder emails.

### 5. 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.

```
function hasAccess(subscriptionStatus) {
  return ['trialing', 'active'].includes(subscriptionStatus);
}
```

**Expected result:** Users in 'trialing' or 'active' status can access premium features; others are restricted.

## Complete code example

File: `trial-subscription.js`

```javascript
const express = require('express');
const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);
const app = express();

// Webhook endpoint — must be before express.json()
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['stripe-signature'];
  let event;
  try {
    event = stripe.webhooks.constructEvent(
      req.body, sig, process.env.STRIPE_WEBHOOK_SECRET
    );
  } catch (err) {
    return res.status(400).send(`Webhook Error: ${err.message}`);
  }

  switch (event.type) {
    case 'customer.subscription.trial_will_end':
      console.log('Trial ending soon:', event.data.object.customer);
      break;
    case 'customer.subscription.updated': {
      const sub = event.data.object;
      console.log(`Subscription ${sub.id} status: ${sub.status}`);
      break;
    }
    case 'invoice.paid':
      console.log('Payment succeeded:', event.data.object.subscription);
      break;
    case 'invoice.payment_failed':
      console.log('Payment failed:', event.data.object.subscription);
      break;
  }
  res.json({ received: true });
});

app.use(express.json());

// Create trial checkout session
app.post('/create-trial', async (req, res) => {
  try {
    const session = await stripe.checkout.sessions.create({
      mode: 'subscription',
      line_items: [{ price: req.body.priceId, quantity: 1 }],
      subscription_data: { trial_period_days: 14 },
      success_url: `${req.headers.origin}/success?session_id={CHECKOUT_SESSION_ID}`,
      cancel_url: `${req.headers.origin}/cancel`,
    });
    res.json({ url: session.url });
  } catch (err) {
    res.status(500).json({ error: err.message });
  }
});

app.listen(4242, () => console.log('Server on port 4242'));
```

## Common mistakes

- **Not handling the trial_will_end webhook** — undefined Fix: Always listen for customer.subscription.trial_will_end to send reminder emails ~3 days before trial expiry.
- **Treating 'trialing' as inactive in your access logic** — undefined Fix: Both 'trialing' and 'active' subscription statuses should grant full access to premium features.
- **Not protecting against trial abuse with multiple signups** — undefined Fix: Use Stripe Radar's trial fraud blocking (stops 62% of abuse), implement email verification, and check for duplicate emails.
- **Using a 30-day trial when 14 days would convert better** — undefined Fix: 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

## 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.

---

Source: https://www.rapidevelopers.com/stripe-guide/how-to-implement-subscription-trials-with-stripe-api
© RapidDev — https://www.rapidevelopers.com/stripe-guide/how-to-implement-subscription-trials-with-stripe-api
