RyzeDeskRyzeDesk

Stripe

4105 articles

Dynamically update trial durations


Dynamically update trial durations

Learn how to modify subscription trial periods during checkout.

Private preview

Dynamic trial updates is in private preview. Request access to dynamic trial updates.

Learn how to dynamically update trial durations on subscription Checkout Sessions.

Use cases

  • Dynamic trial management : Add or remove trials based on promotional conditions or customer actions.
  • Extend trials for upsells : Offer longer trial periods when customers upgrade to higher-tier plans (for example. 7 days for monthly gets extended to 14 days for yearly).

Payment Intents API

If you use the Payment Intents API, you can use the Subscriptions API to adjust trial settings.

Set up the SDK Server-side

Use our official libraries to access the Stripe API from your application:

Command Line

Select a language

Ruby

Python

PHP

Node.js

.NET

Go

Java

No results

gem install stripe -v 15.1.0-beta.2

Update the server SDK Server-side

To use this beta, first update your SDK to use the private preview API version and the checkout_server_update_beta=v1 beta version header.

Select a language

Ruby

Python

PHP

Node

.NET

Go

Java

No results

# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new(
 'sk_test_Ou1w6LVt3zmVipDVJsvMeQsc',
 stripe_version: '2025-03-31.preview; checkout_server_update_beta=v1;',
)

Dynamically update trial durations Server-side

Create an endpoint on your server to update the trial duration for a yearly upsell on the Checkout Session. You call this from the front end in a later step.

Security tip

Client-side code runs in an environment that’s controlled by the user. A malicious user can bypass your client-side validation, intercept and modify requests or create new requests to your server.

When creating an endpoint, we recommend the following:

  • Create endpoints for specific customer interactions instead of making them generic. For example, “extend trial for yearly upgrade” instead of a general “update” action. Specific endpoints can help with writing and maintaining validation logic.
  • Don’t pass session data directly from the client to your endpoint. Malicious clients can modify request data, making it an unreliable source for determining the Checkout Session state. Instead, pass the session ID to your server and use it to securely retrieve the data from the Stripe API.

You can update trial durations using either:

  • trial _ period _ days : An integer that represents the number of days for the trial period or an empty string to remove the trial.
  • trial _ end : A Unix timestamp that represents when the trial should end or an empty string to remove the trial.

Keep in mind the following:

  • The trial _ period _ days and trial _ end parameters are mutually exclusive. You can only specify one of them in a single update request.
  • When removing a trial, use the same field that it was set with. You can only use trial _ period _ days: "" to remove a trial set with trial _ period _ days . You can only use trial _ end: "" to remove a trial set with trial _ end .

Select a language

Ruby

Python

PHP

Node.js

.NET

Go

Java

No results

require 'sinatra'
require 'json'
require 'stripe'

set :port, 4242

# Don't put any keys in code. See https://docs.stripe.com/keys-best-practices.
# Find your keys at https://dashboard.stripe.com/apikeys.
client = Stripe::StripeClient.new(
 'sk_test_Ou1w6LVt3zmVipDVJsvMeQsc',
 stripe_version: '2025-03-31.preview; checkout_server_update_beta=v1;',
)

post '/extend-trial-for-yearly' do
 content_type :json
 request.body.rewind
 request_data = JSON.parse(request.body.read)

 checkout_session_id = request_data['checkout_session_id']

 if checkout_session_id.nil?
 status 400
 return {
 type: 'error',
 message: 'We could not process your request. Please try again later.'
 }.to_json
 end

 begin
 # 1. Retrieve the current session to validate it's a subscription
 session = client.v1.checkout.sessions.retrieve(checkout_session_id)

 unless session.mode == 'subscription'
 status 400
 return {
 type: 'error',
 message: 'Trial updates are only available for subscription sessions.'
 }.to_json
 end

 # 2. Update the Checkout Session with extended trial duration
 client.v1.checkout.sessions.update(checkout_session_id, {
 subscription_data: {
 trial_period_days: 14,
 }
 })

 # 3. Return success response
 { type: 'success' }.to_json
 rescue Stripe::StripeError
 # Handle Stripe errors with a generic error message
 status 400
 {
 type: 'error',
 message: 'We couldn't process your request. Please try again later.'
 }.to_json
 rescue StandardError
 # Handle unexpected errors
 status 500
 {
 type: 'error',
 message: 'Something went wrong on our end. Please try again later.'
 }.to_json
 end
end

Update the client SDK Client-side

Initialise Stripe.js.

checkout.js

const stripe = Stripe('pk_test_GvF3BSyx8RSXMK5yAFhqEd3H');

Request server updates Client-side

From your front end, send an update request to your server and wrap it in runServerUpdate. A successful request updates the Session object with the new trial duration.

Wrap runServerUpdate calls in try / catch blocks to handle errors from your server and from runServerUpdate itself (for example, timeouts). response.type === 'error' covers failures in Stripe’s internal retrieval of the updated session. Errors returned by your own server (such as 4xx or 5xx responses) aren’t reflected in response because the Fetch API resolves for any completed HTTP response regardless of status code. Check response.ok inside your update function and throw on failure so that the catch block is reached.

index.html

<button id="extend-trial-yearly">
 Upgrade to a yearly subscription and get an extended trial
</button>

checkout.js

document.getElementById('extend-trial-yearly')
 .addEventListener("click", async (event) => {
 const updateCheckout = async () => {
 const response = await fetch("/extend-trial-for-yearly", {
 method: "POST",
 headers: {
 "Content-type": "application/json",
 },
 body: JSON.stringify({
 checkout_session_id: actions.getSession().id,
 })
 });
 if (!response.ok) {
 const body = await response.json();
 throw new Error(body.message);
 }
 };

 try {
 const response = await checkout.runServerUpdate(updateCheckout);
 if (response.type === 'error') {
 // Handle Stripe API errors (for example, session retrieval failure)
 return;
 }
 } catch (error) {
 // Handle promise rejection from your server (4xx/5xx errors) or
 // from runServerUpdate itself (for example, timeouts).
 // error.message contains the message thrown from your update function.
 return;
 }

 // Update UI to reflect the extended trial
 event.target.textContent = "Trial extended to 14 days!";
 event.target.disabled = true;
 });

Test the integration

Test your integration to ensure trial duration updates work correctly:

  1. Create a subscription Checkout Session with an initial trial period.
  2. Trigger your server endpoint by interacting with the UI element you created.
  3. Verify the trial duration is updated correctly in the Checkout Session.
  4. Complete the checkout to ensure the subscription is created with the correct trial settings.

Note

When testing, use a sandbox to avoid creating live subscriptions. You can verify the trial duration changes by inspecting the Checkout Session object or the created subscription.

Common testing scenarios

  • Trial extension : Start with a 7-day trial, extend to 14 days, and verify the change is reflected in the UI and session object.
  • Remove existing trial : Start with a 7-day trial, remove it, and verify the change is reflected in the UI and session object. Remove the trial using the same field that it was set with.
  • Error handling : Test invalid requests to ensure your error handling works correctly.
Last verified 2026-09-27

Is this helpful?